# HG changeset patch # User Chris Cannam # Date 1395046442 0 # Node ID e248c7af89ecdc5a4c5bedafdb7c6d357643873e # Parent 261b3d9a4903c28d2b582f13ef5035ad20ba9566 Update to Redmine SVN revision 12979 on 2.4-stable branch diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/0065f42c8aab54558480736376f69f7b72cabc58.svn-base --- a/.svn/pristine/00/0065f42c8aab54558480736376f69f7b72cabc58.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -require 'test/unit' -require 'rubygems' - -gem 'activesupport' -require 'active_support' - -gem 'actionpack' -require 'action_controller' - -gem 'mocha' -require 'mocha' - -gem 'ruby-openid' -require 'openid' - -RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT -require File.dirname(__FILE__) + "/../lib/open_id_authentication" diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/0081b5051789911922bdd0b18a28f0ad87c64e8d.svn-base --- a/.svn/pristine/00/0081b5051789911922bdd0b18a28f0ad87c64e8d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -
-<%= 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/009460d580c5e7ab7c5519f32165ee2250a363d7.svn-base --- a/.svn/pristine/00/009460d580c5e7ab7c5519f32165ee2250a363d7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - - private - - def read_diff_fixture(filename) - File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/0097cb632567412c9ae3b42993114578789fd825.svn-base --- a/.svn/pristine/00/0097cb632567412c9ae3b42993114578789fd825.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/00bd76ee7194e922fe9340f2d39d910c9f79b20e.svn-base --- a/.svn/pristine/00/00bd76ee7194e922fe9340f2d39d910c9f79b20e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -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 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/00dbd874dfe947c78eb0cddc2e6a0cbc57c3f815.svn-base --- a/.svn/pristine/00/00dbd874dfe947c78eb0cddc2e6a0cbc57c3f815.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/00/00e064a58814bd1506484c9e41b6bc1f8553a834.svn-base --- a/.svn/pristine/00/00e064a58814bd1506484c9e41b6bc1f8553a834.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,795 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, - :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 - - 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 - - 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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/013035c9e2d7a0734359dad136ab782f7272366b.svn-base --- a/.svn/pristine/01/013035c9e2d7a0734359dad136ab782f7272366b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -class CreateJournals < ActiveRecord::Migration - - # model removed, but needed for data migration - class IssueHistory < ActiveRecord::Base; belongs_to :issue; end - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - create_table :journals, :force => true do |t| - t.column "journalized_id", :integer, :default => 0, :null => false - t.column "journalized_type", :string, :limit => 30, :default => "", :null => false - t.column "user_id", :integer, :default => 0, :null => false - t.column "notes", :text - t.column "created_on", :datetime, :null => false - end - create_table :journal_details, :force => true do |t| - t.column "journal_id", :integer, :default => 0, :null => false - t.column "property", :string, :limit => 30, :default => "", :null => false - t.column "prop_key", :string, :limit => 30, :default => "", :null => false - t.column "old_value", :string - t.column "value", :string - end - - # indexes - add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id" - add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" - - 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| - 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 - } - - drop_table :issue_histories - end - - def self.down - drop_table :journal_details - drop_table :journals - - create_table "issue_histories", :force => true do |t| - t.column "issue_id", :integer, :default => 0, :null => false - t.column "status_id", :integer, :default => 0, :null => false - t.column "author_id", :integer, :default => 0, :null => false - t.column "notes", :text, :default => "" - t.column "created_on", :timestamp - end - - add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" - - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/0140b2e0d97ff88f8ef3743106e70b9d5c56caec.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/0140b2e0d97ff88f8ef3743106e70b9d5c56caec.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,96 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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' + 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 => 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 auth_sources_path + else + render :action => 'new' + end + end + + def edit + end + + def update + if @auth_source.update_attributes(params[:auth_source]) + flash[:notice] = l(:notice_successful_update) + redirect_to auth_sources_path + else + render :action => 'edit' + end + end + + def test_connection + 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 auth_sources_path + end + + def destroy + unless @auth_source.users.exists? + @auth_source.destroy + flash[:notice] = l(:notice_successful_delete) + end + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/0154cdd03545376764b58ded20a14138238e65d0.svn-base --- a/.svn/pristine/01/0154cdd03545376764b58ded20a14138238e65d0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/018db29c39216709e43cc1f869161f57aa497455.svn-base --- a/.svn/pristine/01/018db29c39216709e43cc1f869161f57aa497455.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/01d08f5b37dfa46aae097b270dbf10baa02d7e48.svn-base --- a/.svn/pristine/01/01d08f5b37dfa46aae097b270dbf10baa02d7e48.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -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.find(:first, :conditions => ["controller=? and action=?", 'projects', 'export_issues_pdf']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'export_pdf']).destroy - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/01df356dd5562f68f76338bddccfd73e2d039ee5.svn-base --- a/.svn/pristine/01/01df356dd5562f68f76338bddccfd73e2d039ee5.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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| - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/01e977490126a0e666397a535bfda934cac273ac.svn-base --- a/.svn/pristine/01/01e977490126a0e666397a535bfda934cac273ac.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,435 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - include Redmine::SafeAttributes - - # Maximum length for repository identifiers - IDENTIFIER_MAX_LENGTH = 255 - - belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" - has_many :filechanges, :class_name => 'Change', :through => :changesets - - serialize :extra_info - - before_save :check_default - - # 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 - validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true - validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? } - 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 - # Checks if the SCM is enabled when creating a repository - validate :repo_create_validation, :on => :create - - safe_attributes 'identifier', - 'login', - 'password', - 'path_encoding', - 'log_encoding', - 'is_default' - - safe_attributes 'url', - :if => lambda {|repository, user| repository.new_record?} - - 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, *args) - attr_name = attribute_key_name.to_s - if attr_name == "log_encoding" - attr_name = "commit_logs_encoding" - end - super(attr_name, *args) - 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 - unless @scm - @scm = self.scm_adapter.new(url, root_url, - login, password, path_encoding) - if root_url.blank? && @scm.root_url.present? - update_attribute(:root_url, @scm.root_url) - end - end - @scm - end - - def scm_name - self.class.scm_name - end - - def name - if identifier.present? - identifier - elsif is_default? - l(:field_repository_is_default) - else - scm_name - end - end - - def identifier=(identifier) - super unless identifier_frozen? - end - - def identifier_frozen? - errors[:identifier].blank? && !(new_record? || identifier.blank?) - end - - def identifier_param - if is_default? - nil - elsif identifier.present? - identifier - else - id.to_s - end - end - - def <=>(repository) - if is_default? - -1 - elsif repository.is_default? - 1 - else - identifier.to_s <=> repository.identifier.to_s - end - end - - def self.find_by_identifier_param(param) - if param.to_s =~ /^\d+$/ - find_by_id(param) - else - find_by_identifier(param) - end - 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) - entries = scm.entries(path, identifier) - load_entries_changesets(entries) - entries - 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? - s = name.to_s - changesets.find(:first, :conditions => (s.match(/^\d*$/) ? - ["revision = ?", s] : ["revision LIKE ?", s + '%'])) - 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 - filechanges.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).all.each do |project| - project.repositories.each do |repository| - begin - 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 - - def set_as_default? - new_record? && project && !Repository.first(:conditions => {:project_id => project.id}) - end - - protected - - def check_default - if !is_default? && set_as_default? - self.is_default = true - end - if is_default? && is_default_changed? - Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id]) - end - end - - def load_entries_changesets(entries) - if entries - entries.each do |entry| - if entry.lastrev && entry.lastrev.identifier - entry.changeset = find_changeset_by_name(entry.lastrev.identifier) - end - end - end - end - - private - - # Deletes repository data - def clear_changesets - cs = Changeset.table_name - ch = Change.table_name - ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}" - cp = "#{table_name_prefix}changeset_parents#{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 #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") - clear_extra_info_of_changesets - end - - def clear_extra_info_of_changesets - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/01/01eefd173bf6e8d083047e854c1bec1880bf6c4e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/01eefd173bf6e8d083047e854c1bec1880bf6c4e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,950 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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. + joins("LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt"). + where("child.id IN (?)", ids). + order("#{Project.table_name}.lft ASC"). + uniq. + all + 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?} + self.class.sort_issues!(issues) + if issues + render_issues(issues, options) + return if abort? + end + versions = project_versions(project) + self.class.sort_versions!(versions) + 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 + self.class.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 + + def self.sort_issues!(issues) + issues.sort! {|a, b| sort_issue_logic(a) <=> sort_issue_logic(b)} + end + + def self.sort_issue_logic(issue) + julian_date = Date.new() + ancesters_start_date = [] + current_issue = issue + begin + ancesters_start_date.unshift([current_issue.start_date || julian_date, current_issue.id]) + current_issue = current_issue.parent + end while (current_issue) + ancesters_start_date + end + + def self.sort_versions!(versions) + versions.sort! + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/02/020539cf91037b37b7739ed19a56c058529a8860.svn-base --- a/.svn/pristine/02/020539cf91037b37b7739ed19a56c058529a8860.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/02/026bb2d46eaa121e79862de3515bb99ff0827ca3.svn-base --- a/.svn/pristine/02/026bb2d46eaa121e79862de3515bb99ff0827ca3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,943 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/02/02795f92f4fa997a818d2a8cff87758b237ae0bf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/02795f92f4fa997a818d2a8cff87758b237ae0bf.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,59 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/02/0282f7d2cfc4f0e2579792f126b478789007d665.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/0282f7d2cfc4f0e2579792f126b478789007d665.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,110 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + test "GET /projects/:project_id/issue_categories.xml should return the 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 + + test "GET /issue_categories/:id.xml should return the 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 + + test "POST /projects/:project_id/issue_categories.xml 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 + + test "POST /projects/:project_id/issue_categories.xml with invalid parameters 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 + + test "PUT /issue_categories/:id.xml with valid parameters should update the 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 + + test "PUT /issue_categories/:id.xml with invalid parameters 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 + + test "DELETE /issue_categories/:id.xml should destroy the issue category" 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 + + test "DELETE /issue_categories/:id.xml should reassign issues with :reassign_to_id param" do + issue_count = Issue.where(:category_id => 1).count + assert issue_count > 0 + + assert_difference 'IssueCategory.count', -1 do + assert_difference 'Issue.where(:category_id => 2).count', 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/02/02c68441083bdea630158440f0e7d9d62ff8f790.svn-base --- a/.svn/pristine/02/02c68441083bdea630158440f0e7d9d62ff8f790.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,822 +0,0 @@ -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/03/03186ac93c7f57779f7557150cdd51f4c66c78c8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/03186ac93c7f57779f7557150cdd51f4c66c78c8.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,34 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::MenuManagerTest < ActiveSupport::TestCase + 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 + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/03/03387de6b294853cc867f560276231ad9e9e4136.svn-base --- a/.svn/pristine/03/03387de6b294853cc867f560276231ad9e9e4136.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -== Redmine upgrade - -Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang -http://www.redmine.org/ - - -== Upgrading - -1. Uncompress the program archive in a new directory - -2. Copy your database settings (RAILS_ROOT/config/database.yml) - and your configuration file (RAILS_ROOT/config/configuration.yml) - into the new config directory - Note: before Redmine 1.2, SMTP configuration was stored in - config/email.yml. It should now be stored in config/configuration.yml. - -3. Copy the RAILS_ROOT/files directory content into your new installation - This directory contains all the attached files. - -4. Copy the folders of the installed plugins and themes into new installation - Plugins must be stored in the [redmine_root]/plugins directory - Themes must be stored in the [redmine_root]/public/themes directory - - WARNING: plugins from your previous Redmine version may not be compatible - with the Redmine version you're upgrading to. - -5. 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 - -6. Generate a session store secret - - Redmine stores session data in cookies by default, which requires - a secret to be generated. Under the new application directory run: - rake generate_secret_token - - DO NOT REPLACE OR EDIT ANY OTHER FILES. - -7. Migrate your database - - If you are upgrading to Rails 2.3.14 as part of this migration, you - need to upgrade the plugin migrations before running the plugin migrations - using: - rake db:migrate:upgrade_plugin_migrations RAILS_ENV="production" - - Please make a backup before doing this! Under the new application - directory run: - rake db:migrate RAILS_ENV="production" - - If you have installed any plugins, you should also run their database - migrations using: - rake db:migrate_plugins RAILS_ENV="production" - -8. Clear the cache and the existing sessions by running: - rake tmp:cache:clear - rake tmp:sessions:clear - -9. Restart the application server (e.g. mongrel, thin, passenger) - -10. Finally go to "Administration -> Roles & permissions" to check/set permissions - for new features, if any - -== References - -* http://www.redmine.org/wiki/redmine/RedmineUpgrade diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/03/038dcd875902d442a622bf1915b8e83469f95936.svn-base --- a/.svn/pristine/03/038dcd875902d442a622bf1915b8e83469f95936.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/03/03b977fa002b4a1e6202e05fed1b5c5f4bee2ed3.svn-base --- a/.svn/pristine/03/03b977fa002b4a1e6202e05fed1b5c5f4bee2ed3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1029 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/03/03da074b61ddfe4ad348c236abb94ecdb763073e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/03da074b61ddfe4ad348c236abb94ecdb763073e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/0406b4818e714c1d680ae173e23dbc29be7a3556.svn-base --- a/.svn/pristine/04/0406b4818e714c1d680ae173e23dbc29be7a3556.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1100 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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 rok" - other: "prawie %{count} lata" - - 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ć kołową 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, - 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: Wprowadzajacy - default_tracker_bug: Błąd - default_tracker_feature: Zadanie - default_tracker_support: Wsparcie - enumeration_activities: Działania (śledzenie czasu) - enumeration_doc_categories: Kategorie dokumentów - enumeration_issue_priorities: Priorytety zagadnień - error_can_t_load_default_data: "Domyślna konfiguracja nie może być załadowana: %{value}" - error_issue_not_found_in_project: 'Zaganienie nie zostało znalezione lub nie należy do tego projektu' - error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacji." - error_scm_command_failed: "Wystąpił błąd przy próbie dostępu do repozytorium: %{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: Przydzielony do - field_attr_firstname: Imię atrybut - field_attr_lastname: Nazwisko atrybut - field_attr_login: Login atrybut - field_attr_mail: Email atrybut - field_auth_source: Tryb identyfikacji - field_author: Autor - field_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 email - field_homepage: Strona www - field_host: Host - field_hours: Godzin - field_identifier: Identifikator - field_is_closed: Zagadnienie zamknięte - field_is_default: Domyślny status - field_is_filter: Atrybut filtrowania - field_is_for_all: Dla wszystkich projektów - field_is_in_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: Email - field_mail_notification: Powiadomienia Email - field_max_length: Maksymalna długość - field_min_length: Minimalna długość - field_name: Nazwa - field_new_password: Nowe hasło - field_notes: Notatki - field_onthefly: Tworzenie użytkownika w locie - field_parent: Nadprojekt - field_parent_title: Strona rodzica - field_password: Hasło - field_password_confirmation: Potwierdzenie - field_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: Start - 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' - 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ę - 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: zablokowane 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 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: 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_plural: Komentarze - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_commits_per_author: Zatwierdzenia według autorów - label_commits_per_month: Zatwierdzenia według miesięcy - label_confirmation: Potwierdzenie - label_contains: zawiera - label_copied: skopiowano - label_copy_workflow_from: Kopiuj przepływ z - label_current_status: Obecny status - label_current_version: Obecna wersja - label_custom_field: Dowolne pole - label_custom_field_new: Nowe dowolne pole - label_custom_field_plural: Dowolne pola - label_date: Data - label_date_from: Z - label_date_range: Zakres datowy - label_date_to: Do - label_day_plural: dni - label_default: Domyślne - label_default_columns: Domyślne kolumny - label_deleted: usunięte - label_details: Szczegóły - label_diff_inline: w linii - label_diff_side_by_side: obok siebie - label_disabled: zablokowany - label_display_per_page: "Na stronę: %{value}" - label_document: Dokument - 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 - 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ść RSS - label_feeds_access_key_created_on: "Klucz dostępu RSS stworzony %{value} dni temu" - label_file_added: Dodano plik - label_file_plural: Pliki - label_filter_add: Dodaj filtr - label_filter_plural: Filtry - label_float: Liczba rzeczywista - label_follows: następuje po - label_gantt: Gantt - label_general: Ogólne - label_generate_key: Wygeneruj klucz - label_help: Pomoc - label_history: Historia - label_home: Główna - label_in: w - label_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_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 - 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: otwarte - label_open_issues_plural: otwarte - label_optional_description: Opcjonalny opis - label_options: Opcje - label_overall_activity: Ogólna aktywność - label_overview: Przegląd - label_password_lost: Zapomniane hasło - label_per_page: Na stronę - label_permissions: Uprawnienia - label_permissions_report: Raport uprawnień - label_personalize_page: Personalizuj tą stronę - label_planning: Planowanie - label_please_login: Zaloguj się - label_plugins: Wtyczki - label_precedes: poprzedza - label_preferences: Preferencje - label_preview: Podgląd - label_previous: Poprzednie - label_project: Projekt - label_project_all: Wszystkie projekty - label_project_latest: Ostatnie projekty - label_project_new: Nowy projekt - label_project_plural234: Projekty - label_project_plural5: Projektów - label_project_plural: Projekty - label_x_projects: - zero: brak projektów - one: jeden projekt - 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: przemianowano - 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 email - 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: "Tylko dla każdego zdarzenia w wybranych projektach..." - label_user_new: Nowy użytkownik - label_user_plural: Użytkownicy - label_version: Wersja - label_version_new: Nowa wersja - label_version_plural: Wersje - label_view_diff: Pokaż różnice - label_view_revisions: Pokaż rewizje - label_watched_issues: Obserwowane zagadnienia - label_week: Tydzień - label_wiki: Wiki - label_wiki_edit: Edycja wiki - label_wiki_edit_plural: Edycje wiki - label_wiki_page: Strona wiki - label_wiki_page_plural: Strony wiki - label_workflow: Przepływ - label_year: Rok - label_yesterday: wczoraj - mail_body_account_activation_request: "Zarejestrowano nowego użytkownika: (%{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_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: Email z instrukcjami zmiany hasła został wysłany do Ciebie. - notice_account_password_updated: Hasło prawidłowo zmienione. - notice_account_pending: "Twoje konto zostało utworzone i oczekuje na zatwierdzenie administratora." - notice_account_register_done: Konto prawidłowo stworzone. - notice_account_unknown_email: Nieznany użytkownik. - notice_account_updated: Konto prawidłowo zaktualizowane. - notice_account_wrong_password: Złe hasło - notice_can_t_change_password: To konto ma zewnętrzne źródło identyfikacji. Nie możesz zmienić hasła. - notice_default_data_loaded: Domyślna konfiguracja została pomyślnie załadowana. - notice_email_error: "Wystąpił błąd w trakcie wysyłania maila (%{value})" - notice_email_sent: "Email 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_file_not_found: Strona do której próbujesz się dostać nie istnieje lub została usunięta. - notice_locking_conflict: Dane poprawione przez innego użytkownika. - notice_no_issue_selected: "Nie wybrano zagadnienia! Zaznacz zagadnienie, które chcesz edytować." - notice_not_authorized: Nie jesteś autoryzowany by zobaczyć stronę. - notice_successful_connection: Udane nawiązanie połączenia. - notice_successful_create: Utworzenie zakończone sukcesem. - notice_successful_delete: Usunięcie zakończone sukcesem. - notice_successful_update: Uaktualnienie zakończone sukcesem. - notice_unable_delete_version: Nie można usunąć wersji - permission_add_issue_notes: Dodawanie notatek - permission_add_issue_watchers: Dodawanie obserwatorów - permission_add_issues: Dodawanie zagadnień - permission_add_messages: Dodawanie wiadomości - permission_browse_repository: Przeglądanie repozytorium - permission_comment_news: Komentowanie komunikatów - permission_commit_access: Wykonywanie zatwierdzeń - permission_delete_issues: Usuwanie zagadnień - permission_delete_messages: Usuwanie wiadomości - permission_delete_wiki_pages: Usuwanie stron wiki - permission_delete_wiki_pages_attachments: Usuwanie załączników - permission_delete_own_messages: Usuwanie własnych wiadomości - permission_edit_issue_notes: Edycja notatek - permission_edit_issues: Edycja zagadnień - permission_edit_messages: Edycja wiadomości - permission_edit_own_issue_notes: Edycja własnych notatek - permission_edit_own_messages: Edycja własnych wiadomości - permission_edit_own_time_entries: Edycja własnego 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 zaganień - permission_manage_documents: Zarządzanie dokumentami - permission_manage_files: Zarządzanie plikami - permission_manage_issue_relations: Zarządzanie powiązaniami zagadnień - permission_manage_members: Zarządzanie uczestnikami - permission_manage_news: Zarządzanie komunikatami - permission_manage_public_queries: Zarządzanie publicznymi kwerendami - permission_manage_repository: Zarządzanie repozytorium - permission_manage_versions: Zarządzanie wersjami - permission_manage_wiki: Zarządzanie wiki - permission_move_issues: Przenoszenie zagadnień - permission_protect_wiki_pages: Blokowanie stron wiki - permission_rename_wiki_pages: Zmiana nazw stron wiki - permission_save_queries: Zapisywanie kwerend - permission_select_project_modules: Wybieranie modułów projektu - permission_view_calendar: Podgląd kalendarza - permission_view_changesets: Podgląd zmian - permission_view_documents: Podgląd dokumentów - permission_view_files: Podgląd plików - permission_view_gantt: Podgląd diagramu Gantta - permission_view_issue_watchers: Podgląd listy obserwatorów - permission_view_messages: Podgląd wiadomości - permission_view_time_entries: Podgląd 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: Auto 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 RSS - setting_gravatar_enabled: Używaj ikon użytkowników Gravatar - setting_host_name: Nazwa hosta i ścieżka - setting_issue_list_default_columns: Domyślne kolumny 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_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_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_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: Wielokrotne wartości dozwolone (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_enumeration_category_reassign_to: 'Zmień przypisanie na tą wartość:' - text_enumeration_destroy_question: "%{count} obiektów jest przypisana do tej wartości." - text_file_repository_writable: Zapisywalne repozytorium plików - text_issue_added: "Zagadnienie %{id} zostało wprowadzone (by %{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_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_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_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_rmagick_available: RMagick dostępne (opcjonalnie) - text_select_mail_notifications: Zaznacz czynności przy których użytkownik powinien być powiadomiony mailem. - text_select_project_modules: 'Wybierz moduły do aktywacji w tym projekcie:' - text_status_changed_by_changeset: "Zastosowane w zmianach %{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_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 - - 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 RSS - label_missing_api_access_key: Brakuje klucza dostępu do API - label_missing_feeds_access_key: Brakuje klucza dostępu do RSS - 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 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 czcionkę - text_zoom_in: Powiększ czcionkę - 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: 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: 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 - 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}) - 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: 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: 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/042891243ed00da7c541ac0d9a5f19f90a344ef6.svn-base --- a/.svn/pristine/04/042891243ed00da7c541ac0d9a5f19f90a344ef6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1133 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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}'을(를) 수정하였습니다." - - 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_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: 지난 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/04353faa53c81964d7239a9938818ad1596cb331.svn-base --- a/.svn/pristine/04/04353faa53c81964d7239a9938818ad1596cb331.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 '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 - - def test_show - get :show, :board_id => 1, :id => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:board) - assert_not_nil assigns(:project) - assert_not_nil assigns(:topic) - end - - def test_show_should_contain_reply_field_tags_for_quoting - @request.session[:user_id] = 2 - get :show, :board_id => 1, :id => 1 - assert_response :success - - # tags required by MessagesController#quote - assert_tag 'input', :attributes => {:id => 'message_subject'} - assert_tag 'textarea', :attributes => {:id => 'message_content'} - assert_tag 'div', :attributes => {:id => 'reply'} - end - - def test_show_with_pagination - message = Message.find(1) - assert_difference 'Message.count', 30 do - 30.times do - message.children << Message.new(:subject => 'Reply', :content => 'Reply body', :author_id => 2, :board_id => 1) - end - end - get :show, :board_id => 1, :id => 1, :r => message.children.last(:order => 'id').id - assert_response :success - assert_template 'show' - replies = assigns(:replies) - assert_not_nil replies - assert !replies.include?(message.children.first(:order => 'id')) - assert replies.include?(message.children.last(:order => 'id')) - end - - def test_show_with_reply_permission - @request.session[:user_id] = 2 - get :show, :board_id => 1, :id => 1 - assert_response :success - assert_template 'show' - assert_tag :div, :attributes => { :id => 'reply' }, - :descendant => { :tag => 'textarea', :attributes => { :id => 'message_content' } } - end - - def test_show_message_not_found - get :show, :board_id => 1, :id => 99999 - assert_response 404 - end - - def test_show_message_from_invalid_board_should_respond_with_404 - get :show, :board_id => 999, :id => 1 - assert_response 404 - end - - def test_get_new - @request.session[:user_id] = 2 - get :new, :board_id => 1 - assert_response :success - assert_template 'new' - end - - def test_post_new - @request.session[:user_id] = 2 - ActionMailer::Base.deliveries.clear - - with_settings :notified_events => %w(message_posted) do - post :new, :board_id => 1, - :message => { :subject => 'Test created message', - :content => 'Message body'} - end - message = Message.find_by_subject('Test created message') - assert_not_nil message - assert_redirected_to "/boards/1/topics/#{message.to_param}" - assert_equal 'Message body', message.content - assert_equal 2, message.author_id - assert_equal 1, message.board_id - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] Test created message", mail.subject - assert_mail_body_match 'Message body', mail - # author - assert mail.bcc.include?('jsmith@somenet.foo') - # project member - assert mail.bcc.include?('dlopper@somenet.foo') - end - - def test_get_edit - @request.session[:user_id] = 2 - get :edit, :board_id => 1, :id => 1 - assert_response :success - assert_template 'edit' - end - - def test_post_edit - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body'} - assert_redirected_to '/boards/1/topics/1' - message = Message.find(1) - assert_equal 'New subject', message.subject - assert_equal 'New body', message.content - end - - def test_post_edit_sticky_and_locked - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body', - :locked => '1', - :sticky => '1'} - assert_redirected_to '/boards/1/topics/1' - message = Message.find(1) - assert_equal true, message.sticky? - assert_equal true, message.locked? - end - - def test_post_edit_should_allow_to_change_board - @request.session[:user_id] = 2 - post :edit, :board_id => 1, :id => 1, - :message => { :subject => 'New subject', - :content => 'New body', - :board_id => 2} - assert_redirected_to '/boards/2/topics/1' - message = Message.find(1) - assert_equal Board.find(2), message.board - end - - def test_reply - @request.session[:user_id] = 2 - post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' } - reply = Message.find(:first, :order => 'id DESC') - assert_redirected_to "/boards/1/topics/1?r=#{reply.id}" - assert Message.find_by_subject('Test reply') - end - - def test_destroy_topic - @request.session[:user_id] = 2 - assert_difference 'Message.count', -3 do - post :destroy, :board_id => 1, :id => 1 - end - assert_redirected_to '/projects/ecookbook/boards/1' - assert_nil Message.find_by_id(1) - end - - def test_destroy_reply - @request.session[:user_id] = 2 - assert_difference 'Message.count', -1 do - post :destroy, :board_id => 1, :id => 2 - end - assert_redirected_to '/boards/1/topics/1?r=2' - assert_nil Message.find_by_id(2) - end - - def test_quote - @request.session[:user_id] = 2 - xhr :get, :quote, :board_id => 1, :id => 3 - assert_response :success - assert_equal 'text/javascript', response.content_type - assert_template 'quote' - assert_include 'RE: First post', response.body - assert_include '> An other reply', response.body - end - - def test_preview_new - @request.session[:user_id] = 2 - post :preview, - :board_id => 1, - :message => {:subject => "", :content => "Previewed text"} - assert_response :success - assert_template 'common/_preview' - end - - def test_preview_edit - @request.session[:user_id] = 2 - post :preview, - :id => 4, - :board_id => 1, - :message => {:subject => "", :content => "Previewed text"} - assert_response :success - assert_template 'common/_preview' - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/046cbe787072732ca9cd18ffb9fce1aa135f723e.svn-base --- a/.svn/pristine/04/046cbe787072732ca9cd18ffb9fce1aa135f723e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -

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

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

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

-

-<%= select_tag 'reassign_to_id', (content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

-
- -<%= submit_tag l(:button_apply) %> -<%= link_to l(:button_cancel), enumerations_path %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/046d428ac69934d1d2d0ae1a160753bb78acb462.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/04/046d428ac69934d1d2d0ae1a160753bb78acb462.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,304 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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_field_format_should_be_validated + field = CustomField.new(:name => 'Test', :field_format => 'foo') + assert !field.valid? + end + + def test_field_format_validation_should_accept_formats_added_at_runtime + Redmine::CustomFieldFormat.register 'foobar' + + field = CustomField.new(:name => 'Some Custom Field', :field_format => 'foobar') + assert field.valid?, 'field should be valid' + ensure + Redmine::CustomFieldFormat.delete 'foobar' + 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?(' ') + 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?(' ') + 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?(' ') + 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?(' ') + 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?(' ') + 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?(' ') + 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?([]) + assert f.valid_field_value?([nil]) + assert f.valid_field_value?(['']) + 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 + + def test_visibile_scope_with_admin_should_return_all_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal 4, CustomField.visible(User.find(1)).count + end + + def test_visibile_scope_with_non_admin_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + user = User.generate! + User.add_to_project(user, Project.first, Role.find(3)) + + assert_equal [fields[0], fields[2]], CustomField.visible(user).order("id").to_a + end + + def test_visibile_scope_with_anonymous_user_should_return_visible_custom_fields + CustomField.delete_all + fields = [ + CustomField.generate!(:visible => true), + CustomField.generate!(:visible => false), + CustomField.generate!(:visible => false, :role_ids => [1, 3]), + CustomField.generate!(:visible => false, :role_ids => [1, 2]), + ] + + assert_equal [fields[0]], CustomField.visible(User.anonymous).order("id").to_a + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/046e745fdf9ac7fc7dec5d1b9e47c29909c11738.svn-base --- a/.svn/pristine/04/046e745fdf9ac7fc7dec5d1b9e47c29909c11738.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -.jstEditor { - padding-left: 0px; -} -.jstEditor textarea, .jstEditor iframe { - margin: 0; -} - -.jstHandle { - height: 10px; - font-size: 0.1em; - cursor: s-resize; - /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/ -} - -.jstElements { - padding: 3px 3px 3px 0; -} - -.jstElements button { - margin-right: 4px; - width : 24px; - height: 24px; - padding: 4px; - border-style: solid; - border-width: 1px; - border-color: #ddd; - background-color : #f7f7f7; - background-position : 50% 50%; - background-repeat: no-repeat; -} -.jstElements button:hover { - border-color: #bbb; - background-color: #e5e5e5; -} -.jstElements button span { - display : none; -} -.jstElements span { - display : inline; -} - -.jstSpacer { - width : 0px; - font-size: 1px; - margin-right: 6px; -} - -.jstElements .help { float: right; margin-right: 0.5em; padding-top: 8px; font-size: 0.9em; } -.jstElements .help a {padding: 2px 0 2px 20px; background: url(../images/help.png) no-repeat 0 50%;} - -/* Buttons --------------------------------------------------------- */ -.jstb_strong { - background-image: url(../images/jstoolbar/bt_strong.png); -} -.jstb_em { - background-image: url(../images/jstoolbar/bt_em.png); -} -.jstb_ins { - background-image: url(../images/jstoolbar/bt_ins.png); -} -.jstb_del { - background-image: url(../images/jstoolbar/bt_del.png); -} -.jstb_code { - background-image: url(../images/jstoolbar/bt_code.png); -} -.jstb_h1 { - background-image: url(../images/jstoolbar/bt_h1.png); -} -.jstb_h2 { - background-image: url(../images/jstoolbar/bt_h2.png); -} -.jstb_h3 { - background-image: url(../images/jstoolbar/bt_h3.png); -} -.jstb_ul { - background-image: url(../images/jstoolbar/bt_ul.png); -} -.jstb_ol { - background-image: url(../images/jstoolbar/bt_ol.png); -} -.jstb_bq { - background-image: url(../images/jstoolbar/bt_bq.png); -} -.jstb_unbq { - background-image: url(../images/jstoolbar/bt_bq_remove.png); -} -.jstb_pre { - background-image: url(../images/jstoolbar/bt_pre.png); -} -.jstb_link { - background-image: url(../images/jstoolbar/bt_link.png); -} -.jstb_img { - background-image: url(../images/jstoolbar/bt_img.png); -} diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/048c8c7c3fc4fac08662cbdaac59cd6348bb3d14.svn-base --- a/.svn/pristine/04/048c8c7c3fc4fac08662cbdaac59cd6348bb3d14.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1287 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'forwardable' -require 'cgi' - -module ApplicationHelper - include Redmine::WikiFormatting::Macros::Definitions - include Redmine::I18n - include GravatarHelper::PublicMethods - - extend Forwardable - def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter - - # Return true if user is authorized for controller/action, otherwise false - def authorize_for(controller, action) - User.current.allowed_to?({:controller => controller, :action => action}, @project) - end - - # Display a link if user is authorized - # - # @param [String] name Anchor text (passed to link_to) - # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized - # @param [optional, Hash] html_options Options passed to link_to - # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to - def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) - link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) - end - - # Displays a link to user's account page if active - def link_to_user(user, options={}) - if user.is_a?(User) - name = h(user.name(options[:format])) - if user.active? || (User.current.admin? && user.logged?) - link_to name, user_path(user), :class => user.css_classes - else - name - end - else - h(user.to_s) - end - end - - # Displays a link to +issue+ with its subject. - # Examples: - # - # link_to_issue(issue) # => Defect #6: This is the subject - # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... - # link_to_issue(issue, :subject => false) # => Defect #6 - # link_to_issue(issue, :project => true) # => Foo - Defect #6 - # link_to_issue(issue, :subject => false, :tracker => false) # => #6 - # - def link_to_issue(issue, options={}) - title = nil - subject = nil - text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" - if options[:subject] == false - title = truncate(issue.subject, :length => 60) - else - subject = issue.subject - if options[:truncate] - subject = truncate(subject, :length => options[:truncate]) - end - end - s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title - s << h(": #{subject}") if subject - s = h("#{issue.project} - ") + s if options[:project] - s - end - - # Generates a link to an attachment. - # Options: - # * :text - Link text (default to attachment filename) - # * :download - Force download (default: false) - def link_to_attachment(attachment, options={}) - text = options.delete(:text) || attachment.filename - action = options.delete(:download) ? 'download' : 'show' - 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) - end - - # Generates a link to a SCM revision - # Options: - # * :text - Link text (default to the formatted revision) - def link_to_revision(revision, repository, options={}) - if repository.is_a?(Project) - repository = repository.repository - end - text = options.delete(:text) || format_revision(revision) - rev = revision.respond_to?(:identifier) ? revision.identifier : revision - link_to( - h(text), - {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, - :title => l(:label_revision_id, format_revision(revision)) - ) - end - - # 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), - :r => (message.parent_id && message.id), - :anchor => (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options), - html_options - ) - end - - # Generates a link to a project if active - # 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) - else - url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) - link_to(h(project), url, html_options) - end - end - - def wiki_page_path(page, options={}) - url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) - 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}, - :title => attachment.filename - end - - def toggle_link(name, id, options={}) - onclick = "$('##{id}').toggle(); " - onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") - onclick << "return false;" - link_to(name, "#", :onclick => onclick) - end - - def image_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - tag(:input, html_options.merge({ - :type => "image", :src => image_path(name), - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) - end - - def format_activity_title(text) - h(truncate_single_line(text, :length => 100)) - end - - def format_activity_day(date) - date == User.current.today ? l(:label_today).titleize : format_date(date) - end - - def format_activity_description(text) - h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') - ).gsub(/[\r\n]+/, "
").html_safe - end - - def format_version_name(version) - if version.project == @project - h(version) - else - h("#{version.project} - #{version}") - end - end - - def due_date_distance_in_words(date) - if date - l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) - end - end - - # Renders a tree of projects as a nested set of unordered lists - # The given collection may be a subset of the whole project tree - # (eg. some intermediate nodes are private and can not be seen) - def render_project_nested_lists(projects) - s = '' - if projects.any? - ancestors = [] - original_project = @project - projects.sort_by(&:lft).each do |project| - # set the project environment to please macros. - @project = project - if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) - s << "\n" - end - end - classes = (ancestors.empty? ? 'root' : 'child') - s << "
  • " - s << h(block_given? ? yield(project) : project.name) - s << "
    \n" - ancestors << project - end - s << ("
  • \n" * ancestors.size) - @project = original_project - end - s.html_safe - end - - def render_page_hierarchy(pages, node=nil, options={}) - content = '' - if pages[node] - content << "\n" - end - content.html_safe - end - - # Renders flash messages - def render_flash_messages - s = '' - flash.each do |k,v| - s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") - end - s.html_safe - end - - # Renders tabs and their content - def render_tabs(tabs) - if tabs.any? - render :partial => 'common/tabs', :locals => {:tabs => tabs} - else - content_tag 'p', l(:label_no_data), :class => "nodata" - end - end - - # Renders the project quick-jump box - def render_project_jump_box - return unless User.current.logged? - projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq - if projects.any? - options = - ("" + - '').html_safe - - options << project_tree_options_for_select(projects, :selected => @project) do |p| - { :value => project_path(:id => p, :jump => current_menu_item) } - end - - select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') - end - end - - def project_tree_options_for_select(projects, options = {}) - s = '' - project_tree(projects) do |project, level| - name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe - tag_options = {:value => project.id} - if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) - tag_options[:selected] = 'selected' - else - tag_options[:selected] = nil - end - tag_options.merge!(yield(project)) if block_given? - s << content_tag('option', name_prefix + h(project), tag_options) - end - s.html_safe - end - - # Yields the given block for each project with its level in the tree - # - # Wrapper for Project#project_tree - def project_tree(projects, &block) - Project.project_tree(projects, &block) - end - - def principals_check_box_tags(name, principals) - s = '' - principals.sort.each do |principal| - s << "\n" - end - s.html_safe - end - - # Returns a string for users/groups option tags - def principals_options_for_select(collection, selected=nil) - s = '' - if collection.include?(User.current) - s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) - end - groups = '' - collection.sort.each do |element| - selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) - (element.is_a?(Group) ? groups : s) << %() - end - unless groups.empty? - s << %(#{groups}) - end - s.html_safe - end - - # Options for the new membership projects combo-box - 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)} - end - options - end - - # Truncates and returns the string as a single line - def truncate_single_line(string, *args) - truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') - end - - # Truncates at line break after 250 characters or options[:length] - def truncate_lines(string, options={}) - length = options[:length] || 250 - if string.to_s =~ /\A(.{#{length}}.*?)$/m - "#{$1}..." - else - string - end - end - - def anchor(text) - text.to_s.gsub(' ', '_') - end - - def html_hours(text) - text.gsub(%r{(\d+)\.(\d+)}, '\1.\2').html_safe - end - - def authoring(created, author, options={}) - l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe - end - - def time_tag(time) - text = distance_of_time_in_words(Time.now, time) - if @project - link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) - else - content_tag('acronym', text, :title => format_time(time)) - end - end - - def syntax_highlight_lines(name, content) - lines = [] - syntax_highlight(name, content).each_line { |line| lines << line } - lines - end - - def syntax_highlight(name, content) - Redmine::SyntaxHighlighting.highlight_by_filename(content, name) - end - - def to_path_param(path) - str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") - 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'}), - :method => method, :title => l(:label_sort_highest)) + - link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), - url.merge({"#{name}[move_to]" => 'higher'}), - :method => method, :title => l(:label_sort_higher)) + - link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), - url.merge({"#{name}[move_to]" => 'lower'}), - :method => method, :title => l(:label_sort_lower)) + - link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), - url.merge({"#{name}[move_to]" => 'lowest'}), - :method => method, :title => l(:label_sort_lowest)) - end - - def breadcrumb(*args) - elements = args.flatten - elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil - end - - def other_formats_links(&block) - concat('

    '.html_safe + l(:label_export_to)) - yield Redmine::Views::OtherFormatsBuilder.new(self) - concat('

    '.html_safe) - end - - def page_header_title - if @project.nil? || @project.new_record? - h(Setting.app_title) - else - b = [] - ancestors = (@project.root? ? [] : @project.ancestors.visible.all) - if ancestors.any? - root = ancestors.shift - b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') - if ancestors.size > 2 - b << "\xe2\x80\xa6" - ancestors = ancestors[-2, 2] - end - b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } - end - b << h(@project) - b.join(" \xc2\xbb ").html_safe - end - end - - def html_title(*args) - if args.empty? - title = @html_title || [] - title << @project.name if @project - title << Setting.app_title unless Setting.app_title == title.last - title.select {|t| !t.blank? }.join(' - ') - else - @html_title ||= [] - @html_title += args - end - end - - # Returns the theme, controller name, and action as css classes for the - # HTML body. - def body_css_classes - css = [] - if theme = Redmine::Themes.theme(Setting.ui_theme) - css << 'theme-' + theme.name - end - - css << 'controller-' + controller_name - css << 'action-' + action_name - css.join(' ') - end - - def accesskey(s) - Redmine::AccessKeys.key_for s - end - - # Formats text according to system settings. - # 2 ways to call this method: - # * with a String: textilizable(text, options) - # * with an object and one of its attribute: textilizable(issue, :description, options) - def textilizable(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - case args.size - when 1 - obj = options[:object] - text = args.shift - when 2 - obj = args.shift - attr = args.shift - text = obj.send(attr).to_s - else - raise ArgumentError, 'invalid arguments to textilizable' - end - return '' if text.blank? - project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) - only_path = options.delete(:only_path) == false ? false : true - - text = text.dup - macros = catch_macros(text) - text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) - - @parsed_headings = [] - @heading_anchors = {} - @current_section = 0 if options[:edit_section_links] - - parse_sections(text, project, obj, attr, only_path, options) - text = parse_non_pre_blocks(text, obj, macros) do |text| - [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| - send method_name, text, project, obj, attr, only_path, options - end - end - parse_headings(text, project, obj, attr, only_path, options) - - if @parsed_headings.any? - replace_toc(text, @parsed_headings) - end - - text.html_safe - end - - def parse_non_pre_blocks(text, obj, macros) - s = StringScanner.new(text) - tags = [] - parsed = '' - while !s.eos? - s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) - text, full_tag, closing, tag = s[1], s[2], s[3], s[4] - if tags.empty? - yield text - inject_macros(text, obj, macros) if macros.any? - else - inject_macros(text, obj, macros, false) if macros.any? - end - parsed << text - if tag - if closing - if tags.last == tag.downcase - tags.pop - end - else - tags << tag.downcase - end - parsed << full_tag - end - end - # Close any non closing tags - while tag = tags.pop - parsed << "" - end - parsed - end - - def parse_inline_attachments(text, project, obj, attr, only_path, options) - # when using an image link, try to use an attachment, if possible - attachments = options[:attachments] || [] - attachments += obj.attachments if obj.respond_to?(:attachments) - if attachments.present? - text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| - 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 - desc = found.description.to_s.gsub('"', '') - if !desc.blank? && alttext.blank? - alt = " title=\"#{desc}\" alt=\"#{desc}\"" - end - "src=\"#{image_url}\"#{alt}" - else - m - end - end - end - end - - # Wiki links - # - # Examples: - # [[mypage]] - # [[mypage|mytext]] - # wiki links can refer other project wikis, using project name or identifier: - # [[project:]] -> wiki starting page - # [[project:|mytext]] - # [[project:mypage]] - # [[project:mypage|mytext]] - def parse_wiki_links(text, project, obj, attr, only_path, options) - text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| - link_project = project - 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? - end - - if link_project && link_project.wiki - # extract anchor - anchor = nil - if page =~ /^(.+?)\#(.+)$/ - page, anchor = $1, $2 - end - anchor = sanitize_anchor_name(anchor) if anchor.present? - # check if page exists - wiki_page = link_project.wiki.find_page(page) - url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page - "##{anchor}" - else - case options[:wiki_links] - when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') - when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export - else - wiki_page_id = page.present? ? Wiki.titleize(page) : nil - parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil - url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, - :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) - end - end - link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) - else - # project or wiki doesn't exist - all - end - else - all - end - end - end - - # Redmine links - # - # Examples: - # Issues: - # #52 -> Link to issue #52 - # Changesets: - # r52 -> Link to revision 52 - # commit:a85130f -> Link to scmid starting with a85130f - # Documents: - # document#17 -> Link to document with id 17 - # document:Greetings -> Link to the document with title "Greetings" - # document:"Some document" -> Link to the document with title "Some document" - # Versions: - # version#3 -> Link to version with id 3 - # version:1.0.0 -> Link to version named "1.0.0" - # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" - # Attachments: - # attachment:file.zip -> Link to the attachment of the current object named file.zip - # Source files: - # source:some/file -> Link to the file located at /some/file in the project's repository - # source:some/file@52 -> Link to the file's revision 52 - # source:some/file#L120 -> Link to line 120 of the file - # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 - # export:some/file -> Force the download of the file - # Forum messages: - # message#1218 -> Link to message with id 1218 - # - # Links can refer other objects from other projects, using project identifier: - # identifier:r52 - # identifier:document:"Some document" - # identifier:version:1.0.0 - # identifier:source:some/file - def parse_redmine_links(text, default_project, obj, attr, only_path, options) - text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| - leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 - link = nil - project = default_project - if project_identifier - project = Project.visible.find_by_identifier(project_identifier) - end - if esc.nil? - if prefix.nil? && sep == 'r' - if project - repository = nil - if repo_identifier - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - # project.changesets.visible raises an SQL error because of a double join on repositories - if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) - link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100)) - end - end - elsif sep == '#' - oid = identifier.to_i - case prefix - when nil - if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) - anchor = comment_id ? "note-#{comment_id}" : nil - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, - :class => issue.css_classes, - :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") - end - when 'document' - if document = Document.visible.find_by_id(oid) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if version = Version.visible.find_by_id(oid) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'message' - if message = Message.visible.find_by_id(oid, :include => :parent) - link = link_to_message(message, {:only_path => only_path}, :class => 'message') - end - when 'forum' - if board = Board.visible.find_by_id(oid) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if news = News.visible.find_by_id(oid) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'project' - if p = Project.visible.find_by_id(oid) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end - end - elsif sep == ':' - # removes the double quotes if any - name = identifier.gsub(%r{^"(.*)"$}, "\\1") - case prefix - when 'document' - if project && document = project.documents.visible.find_by_title(name) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if project && version = project.versions.visible.find_by_name(name) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'forum' - if project && board = project.boards.visible.find_by_name(name) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if project && news = project.news.visible.find_by_title(name) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'commit', 'source', 'export' - if project - repository = nil - if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} - repo_prefix, repo_identifier, name = $1, $2, $3 - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - if prefix == 'commit' - if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) - 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) - end - else - if repository && User.current.allowed_to?(:browse_repository, project) - name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(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), - :rev => rev, - :anchor => anchor}, - :class => (prefix == 'export' ? 'source download' : 'source') - end - end - repo_prefix = nil - end - 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' - end - when 'project' - if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end - end - end - end - (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) - end - end - - HEADING_RE = /(]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) - - def parse_sections(text, project, obj, attr, only_path, options) - return unless options[:edit_section_links] - text.gsub!(HEADING_RE) do - heading = $1 - @current_section += 1 - if @current_section > 1 - content_tag('div', - link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), - :class => 'contextual', - :title => l(:button_edit_section)) + heading.html_safe - else - heading - end - end - end - - # Headings and TOC - # Adds ids and links to headings unless options[:headings] is set to false - def parse_headings(text, project, obj, attr, only_path, options) - return if options[:headings] == false - - text.gsub!(HEADING_RE) do - level, attrs, content = $2.to_i, $3, $4 - item = strip_tags(content).strip - anchor = sanitize_anchor_name(item) - # used for single-file wiki export - anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) - @heading_anchors[anchor] ||= 0 - idx = (@heading_anchors[anchor] += 1) - if idx > 1 - anchor = "#{anchor}-#{idx}" - end - @parsed_headings << [level, anchor, item] - "\n#{content}" - end - end - - MACROS_RE = /( - (!)? # escaping - ( - \{\{ # opening tag - ([\w]+) # macro name - (\(([^\n\r]*?)\))? # optional arguments - ([\n\r].*?[\n\r])? # optional block of text - \}\} # closing tag - ) - )/mx unless const_defined?(:MACROS_RE) - - MACRO_SUB_RE = /( - \{\{ - macro\((\d+)\) - \}\} - )/x unless const_defined?(:MACRO_SUB_RE) - - # Extracts macros from text - def catch_macros(text) - macros = {} - text.gsub!(MACROS_RE) do - all, macro = $1, $4.downcase - if macro_exists?(macro) || all =~ MACRO_SUB_RE - index = macros.size - macros[index] = all - "{{macro(#{index})}}" - else - all - end - end - macros - end - - # Executes and replaces macros in text - def inject_macros(text, obj, macros, execute=true) - text.gsub!(MACRO_SUB_RE) do - all, index = $1, $2.to_i - orig = macros.delete(index) - if execute && orig && orig =~ MACROS_RE - esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) - if esc.nil? - h(exec_macro(macro, obj, args, block) || all) - else - h(all) - end - elsif orig - h(orig) - else - h(all) - end - end - end - - TOC_RE = /

    \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) - - # Renders the TOC with given headings - def replace_toc(text, headings) - text.gsub!(TOC_RE) do - # Keep only the 4 first levels - headings = headings.select{|level, anchor, item| level <= 4} - if headings.empty? - '' - else - div_class = 'toc' - div_class << ' right' if $1 == '>' - div_class << ' left' if $1 == '<' - out = "

    • " - root = headings.map(&:first).min - current = root - started = false - headings.each do |level, anchor, item| - if level > current - out << '
      • ' * (level - current) - elsif level < current - out << "
      \n" * (current - level) + "
    • " - elsif started - out << '
    • ' - end - out << "#{item}" - current = level - started = true - end - out << '
    ' * (current - root) - out << '' - end - end - end - - # Same as Rails' simple_format helper without using paragraphs - def simple_format_without_paragraph(text) - text.to_s. - gsub(/\r\n?/, "\n"). # \r\n and \r -> \n - gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br - gsub(/([^\n]\n)(?=[^\n])/, '\1
    '). # 1 newline -> br - html_safe - end - - def lang_options_for_select(blank=true) - (blank ? [["(auto)", ""]] : []) + languages_options - end - - def label_tag_for(name, option_tags = nil, options = {}) - label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") - content_tag("label", label_text) - end - - def labelled_form_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - if args.first.is_a?(Symbol) - options.merge!(:as => args.shift) - end - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - form_for(*args, &proc) - end - - def labelled_fields_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - fields_for(*args, &proc) - end - - def labelled_remote_form_for(*args, &proc) - ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) - form_for(*args, &proc) - end - - def error_messages_for(*objects) - html = "" - objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact - errors = objects.map {|o| o.errors.full_messages}.flatten - if errors.any? - html << "
      \n" - errors.each do |error| - html << "
    • #{h error}
    • \n" - end - html << "
    \n" - end - html.html_safe - end - - def delete_link(url, options={}) - options = { - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del' - }.merge(options) - - link_to l(:button_delete), url, options - end - - def preview_link(url, form, target='preview', options={}) - content_tag 'a', l(:label_preview), { - :href => "#", - :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, - :accesskey => accesskey(:preview) - }.merge(options) - end - - def link_to_function(name, function, html_options={}) - content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) - end - - # Helper to render JSON in views - def raw_json(arg) - arg.to_json.to_s.gsub('/', '\/').html_safe - end - - def back_url - url = params[:back_url] - if url.nil? && referer = request.env['HTTP_REFERER'] - url = CGI.unescape(referer.to_s) - end - url - end - - def back_url_hidden_field_tag - url = back_url - hidden_field_tag('back_url', url, :id => nil) unless url.blank? - end - - def check_all_links(form_name) - link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + - " | ".html_safe + - link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") - end - - def progress_bar(pcts, options={}) - pcts = [pcts, pcts] unless pcts.is_a?(Array) - pcts = pcts.collect(&:round) - pcts[1] = pcts[1] - pcts[0] - pcts << (100 - pcts[1] - pcts[0]) - width = options[:width] || '100px;' - legend = options[:legend] || '' - content_tag('table', - content_tag('tr', - (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + - (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 - end - - def checked_image(checked=true) - if checked - image_tag 'toggle_check.png' - end - end - - def context_menu(url) - unless @context_menu_included - content_for :header_tags do - javascript_include_tag('context_menu') + - stylesheet_link_tag('context_menu') - end - if l(:direction) == 'rtl' - content_for :header_tags do - stylesheet_link_tag('context_menu_rtl') - end - end - @context_menu_included = true - end - javascript_tag "contextMenuInit('#{ url_for(url) }')" - end - - def calendar_for(field_id) - include_calendar_headers_tags - javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") - end - - def include_calendar_headers_tags - unless @calendar_headers_tags_included - @calendar_headers_tags_included = true - content_for :header_tags do - start_of_week = Setting.start_of_week - start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? - # Redmine uses 1..7 (monday..sunday) in settings and locales - # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 - start_of_week = start_of_week.to_i % 7 - - tags = javascript_tag( - "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + - "showOn: 'button', buttonImageOnly: true, buttonImage: '" + - path_to_image('/images/calendar.png') + - "', showButtonPanel: 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") - end - tags - end - end - end - - # Overrides Rails' stylesheet_link_tag with themes and plugins support. - # Examples: - # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults - # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets - # - def stylesheet_link_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/stylesheets/#{source}" - elsif current_theme && current_theme.stylesheets.include?(source) - current_theme.stylesheet_path(source) - else - source - end - end - super sources, options - end - - # Overrides Rails' image_tag with themes and plugins support. - # Examples: - # image_tag('image.png') # => picks image.png from the current theme or defaults - # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets - # - def image_tag(source, options={}) - if plugin = options.delete(:plugin) - source = "/plugin_assets/#{plugin}/images/#{source}" - elsif current_theme && current_theme.images.include?(source) - source = current_theme.image_path(source) - end - super source, options - end - - # Overrides Rails' javascript_include_tag with plugins support - # Examples: - # javascript_include_tag('scripts') # => picks scripts.js from defaults - # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets - # - def javascript_include_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - if plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/javascripts/#{source}" - else - source - end - end - end - super sources, options - end - - def content_for(name, content = nil, &block) - @has_content ||= {} - @has_content[name] = true - super(name, content, &block) - end - - def has_content?(name) - (@has_content && @has_content[name]) || false - end - - def sidebar_content? - has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? - end - - def view_layouts_base_sidebar_hook_response - @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) - end - - def email_delivery_enabled? - !!ActionMailer::Base.perform_deliveries - end - - # Returns the avatar image tag for the given +user+ if avatars are enabled - # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') - def avatar(user, options = { }) - if Setting.gravatar_enabled? - options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) - email = nil - if user.respond_to?(:mail) - email = user.mail - elsif user.to_s =~ %r{<(.+?)>} - email = $1 - end - return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil - else - '' - end - end - - def sanitize_anchor_name(anchor) - if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' - anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') - else - # TODO: remove when ruby1.8 is no longer supported - anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') - end - end - - # 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') - 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 - tags - end - - def favicon - "".html_safe - end - - def robot_exclusion_tag - ''.html_safe - end - - # Returns true if arg is expected in the API response - def include_in_api_response?(arg) - unless @included_in_api_response - param = params[:include] - @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') - @included_in_api_response.collect!(&:strip) - end - @included_in_api_response.include?(arg.to_s) - end - - # Returns options or nil if nometa param or X-Redmine-Nometa header - # was set in the request - def api_meta(options) - if params[:nometa].present? || request.headers['X-Redmine-Nometa'] - # compatibility mode for activeresource clients that raise - # an error when unserializing an array with attributes - nil - else - options - end - end - - private - - def wiki_helper - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper - return self - end - - def link_to_content_update(text, url_params = {}, html_options = {}) - link_to(text, url_params, html_options) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/04dcbe1c1303811a184d8f2171e27c9cdde0c767.svn-base --- a/.svn/pristine/04/04dcbe1c1303811a184d8f2171e27c9cdde0c767.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1091 +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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/04/04dfe98ce0a611eee30923688999ac146c40ca35.svn-base --- a/.svn/pristine/04/04dfe98ce0a611eee30923688999ac146c40ca35.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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}" - - 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 :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) - 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) - 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 - } - - 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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/05/0513c24a062b523037219d9978aab2b119808571.svn-base --- a/.svn/pristine/05/0513c24a062b523037219d9978aab2b119808571.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -<%= form_for @project, - :url => { :action => 'modules', :id => @project }, - :html => {:id => 'modules-form', - :method => :post} do |f| %> - -
    -
    -<%= l(:text_select_project_modules) %> - -<% Redmine::AccessControl.available_project_modules.each do |m| %> -

    -<% end %> -
    -
    - -

    <%= check_all_links 'modules-form' %>

    -

    <%= submit_tag l(:button_save) %>

    - -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/05/0548f38a902595a77747491d864f451fe12f0f47.svn-base --- a/.svn/pristine/05/0548f38a902595a77747491d864f451fe12f0f47.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,346 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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' - match 'logout', :to => 'account#logout', :as => 'signout' - 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 '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] - 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/: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/: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' - - match '/projects/:project_id/issues/calendar', :to => 'calendars#show' - match '/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 - - 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' - - 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" - - resources :projects do - member do - get 'settings' - post 'modules' - post 'archive' - post 'unarchive' - post 'close' - post 'reopen' - match 'copy', :via => [:get, :post] - end - - resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do - get 'autocomplete' - end - end - - resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] - - match 'issues/:copy_from/copy', :to => 'issues#new' - 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/new', :controller => 'issues', :action => 'new', :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 - match 'versions.:format', :to => 'versions#index' - match 'roadmap', :to => 'versions#index', :format => false - match '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 - resources :documents, :except => [:show, :edit, :update, :destroy] - resources :boards - resources :repositories, :shallow => true, :except => [:index, :show] do - member do - match 'committers', :via => [:get, :post] - end - end - - match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get - resources :wiki, :except => [:index, :new, :create] 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' - 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 - resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] - 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 - - 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 - - # 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' - - # 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 - 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 - match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ - 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' - 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] - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/05/05bd9a477032dec0ee79cadb6f6626cf79ccae69.svn-base --- a/.svn/pristine/05/05bd9a477032dec0ee79cadb6f6626cf79ccae69.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,431 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingRepositoriesTest < ActionController::IntegrationTest - def setup - @path_hash = repository_path_hash(%w[path to file.c]) - assert_equal "path/to/file.c", @path_hash[:path] - assert_equal "path/to/file.c", @path_hash[:param] - end - - def test_repositories_resources - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repositories/new" }, - { :controller => 'repositories', :action => 'new', :project_id => 'redmine' } - ) - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repositories" }, - { :controller => 'repositories', :action => 'create', :project_id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/repositories/1/edit" }, - { :controller => 'repositories', :action => 'edit', :id => '1' } - ) - assert_routing( - { :method => 'put', - :path => "/repositories/1" }, - { :controller => 'repositories', :action => 'update', :id => '1' } - ) - assert_routing( - { :method => 'delete', - :path => "/repositories/1" }, - { :controller => 'repositories', :action => 'destroy', :id => '1' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, - :path => "/repositories/1/committers" }, - { :controller => 'repositories', :action => 'committers', :id => '1' } - ) - end - end - - def test_repositories_show - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine' } - ) - end - - def test_repositories - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/statistics" }, - { :controller => 'repositories', :action => 'stats', :id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/graph" }, - { :controller => 'repositories', :action => 'graph', :id => 'redmine' } - ) - end - - def test_repositories_show_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo' } - ) - end - - def test_repositories_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/statistics" }, - { :controller => 'repositories', :action => 'stats', :id => 'redmine', :repository_id => 'foo' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/graph" }, - { :controller => 'repositories', :action => 'graph', :id => 'redmine', :repository_id => 'foo' } - ) - end - - def test_repositories_revisions - empty_path_param = [] - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions.atom" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/show" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/show/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', - :path => @path_hash[:param] , :rev => '2457'} - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :rev => '2457', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param], :rev => '2', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revisions/2/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', - :path => @path_hash[:param], :rev => '2' } - ) - end - - def test_repositories_revisions_with_repository_id - empty_path_param = [] - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions.atom" }, - { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo', - :format => 'atom' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/show" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/show/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] , :rev => '2457'} - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :rev => '2457' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2457/diff" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :rev => '2457', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2', :format => 'diff' }, - {}, - { :format => 'diff' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revisions/2/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param], :rev => '2' } - ) - end - - def test_repositories_non_revisions_path - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine' } - ) - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :rev => rev }, - {}, - { :rev => rev } - ) - end - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :path => @path_hash[:param], :rev => rev }, - {}, - { :rev => rev } - ) - end - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/browse/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'browse', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/revision" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine' } - ) - end - - def test_repositories_non_revisions_path_with_repository_id - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes" }, - { :controller => 'repositories', :action => 'changes', - :id => 'redmine', :repository_id => 'foo' } - ) - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes" }, - { :controller => 'repositories', :action => 'changes', - :id => 'redmine', - :repository_id => 'foo', :rev => rev }, - {}, - { :rev => rev } - ) - end - ['2457', 'master', 'slash/slash'].each do |rev| - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', - :repository_id => 'foo', :path => @path_hash[:param], :rev => rev }, - {}, - { :rev => rev } - ) - end - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/diff/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/browse/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'browse', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/entry/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/raw/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'raw', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/annotate/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/changes/#{@path_hash[:path]}" }, - { :controller => 'repositories', :action => 'changes', :id => 'redmine', :repository_id => 'foo', - :path => @path_hash[:param] } - ) - assert_routing( - { :method => 'get', - :path => "/projects/redmine/repository/foo/revision" }, - { :controller => 'repositories', :action => 'revision', :id => 'redmine', :repository_id => 'foo'} - ) - end - - def test_repositories_related_issues - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repository/revisions/123/issues" }, - { :controller => 'repositories', :action => 'add_related_issue', - :id => 'redmine', :rev => '123' } - ) - assert_routing( - { :method => 'delete', - :path => "/projects/redmine/repository/revisions/123/issues/25" }, - { :controller => 'repositories', :action => 'remove_related_issue', - :id => 'redmine', :rev => '123', :issue_id => '25' } - ) - end - - def test_repositories_related_issues_with_repository_id - assert_routing( - { :method => 'post', - :path => "/projects/redmine/repository/foo/revisions/123/issues" }, - { :controller => 'repositories', :action => 'add_related_issue', - :id => 'redmine', :repository_id => 'foo', :rev => '123' } - ) - assert_routing( - { :method => 'delete', - :path => "/projects/redmine/repository/foo/revisions/123/issues/25" }, - { :controller => 'repositories', :action => 'remove_related_issue', - :id => 'redmine', :repository_id => 'foo', :rev => '123', :issue_id => '25' } - ) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/06/06191abb72622bec9f1387de935e8ce335158e32.svn-base --- a/.svn/pristine/06/06191abb72622bec9f1387de935e8ce335158e32.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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_container_for_new_attachment_should_be_nil - assert_nil Attachment.new.container - 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_size_should_be_validated_for_new_file - with_settings :attachment_max_size => 0 do - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert !a.save - end - end - - def test_size_should_not_be_validated_when_copying - a = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - with_settings :attachment_max_size => 0 do - copy = a.copy - assert copy.save - end - end - - def test_description_length_should_be_validated - a = Attachment.new(:description => 'a' * 300) - assert !a.save - assert_not_nil a.errors[:description] - 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_destroy_should_not_delete_file_referenced_by_other_attachment - a = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - diskfile = a.diskfile - - copy = a.copy - copy.save! - - assert File.exists?(diskfile) - a.destroy - assert File.exists?(diskfile) - copy.destroy - assert !File.exists?(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 - - def test_title - a = Attachment.new(:filename => "test.png") - assert_equal "test.png", a.title - - a = Attachment.new(:filename => "test.png", :description => "Cool image") - assert_equal "test.png (Cool image)", a.title - end - - def test_prune_should_destroy_old_unattached_attachments - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) - Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1) - - assert_difference 'Attachment.count', -2 do - Attachment.prune - 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 - - 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.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 - end - - def test_latest_attach - set_fixtures_attachments_directory - 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 - - def test_thumbnailable_should_be_true_for_images - assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable? - end - - def test_thumbnailable_should_be_true_for_non_images - assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable? - end - - if convert_installed? - def test_thumbnail_should_generate_the_thumbnail - set_fixtures_attachments_directory - attachment = Attachment.find(16) - Attachment.clear_thumbnails - - assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do - thumbnail = attachment.thumbnail - assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail) - assert File.exists?(thumbnail) - end - end - else - puts '(ImageMagick convert not available)' - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/06/06594ea20a7f9dbceb45bfdd779a780237719c5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/06/06594ea20a7f9dbceb45bfdd779a780237719c5b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,123 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 IssueStatusesControllerTest < ActionController::TestCase + fixtures :issue_statuses, :issues, :users + + 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%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.order('id DESC').first + assert_equal 'New status', status.name + end + + def test_create_with_failure + post :create, :issue_status => {:name => ''} + assert_response :success + assert_template 'new' + assert_error_tag :content => /name can't be blank/i + 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_update_with_failure + put :update, :id => '3', :issue_status => {:name => ''} + assert_response :success + assert_template 'edit' + assert_error_tag :content => /name can't be blank/i + 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 + + def test_update_issue_done_ratio_with_issue_done_ratio_set_to_issue_field + with_settings :issue_done_ratio => 'issue_field' do + post :update_issue_done_ratio + assert_match /not updated/, flash[:error].to_s + assert_redirected_to '/issue_statuses' + end + end + + def test_update_issue_done_ratio_with_issue_done_ratio_set_to_issue_status + with_settings :issue_done_ratio => 'issue_status' do + post :update_issue_done_ratio + assert_match /Issue done ratios updated/, flash[:notice].to_s + assert_redirected_to '/issue_statuses' + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/06/0661103167f6b9ecf574c505f5bd6a17eadf893e.svn-base --- a/.svn/pristine/06/0661103167f6b9ecf574c505f5bd6a17eadf893e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,703 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/06/06dd3ee8641385912fee02a84e6de53eed535f28.svn-base --- a/.svn/pristine/06/06dd3ee8641385912fee02a84e6de53eed535f28.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -
    -<%= 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), {:action => 'test_connection', :id => source}, :class => 'icon icon-test' %> - <%= delete_link auth_source_path(source) %> -
    - -

    <%= pagination_links_full @auth_source_pages %>

    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/071604c5dacdea2b6b0884f22cd68afddd0f1a2a.svn-base --- a/.svn/pristine/07/071604c5dacdea2b6b0884f22cd68afddd0f1a2a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/071c1af1d5f365dd709aaf606fafd0332e258d64.svn-base --- a/.svn/pristine/07/071c1af1d5f365dd709aaf606fafd0332e258d64.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,476 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/072d65dcf460d00cc4770641f038bc5801ca9243.svn-base --- a/.svn/pristine/07/072d65dcf460d00cc4770641f038bc5801ca9243.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 PrincipalTest < ActiveSupport::TestCase - fixtures :users, :projects, :members, :member_roles - - def test_active_scope_should_return_groups_and_active_users - result = Principal.active.all - assert_include Group.first, result - assert_not_nil result.detect {|p| p.is_a?(User)} - assert_nil result.detect {|p| p.is_a?(User) && !p.active?} - assert_nil result.detect {|p| p.is_a?(AnonymousUser)} - end - - def test_member_of_scope_should_return_the_union_of_all_members - projects = Project.find_all_by_id(1, 2) - assert_equal projects.map(&:principals).flatten.sort, Principal.member_of(projects).sort - end - - def test_member_of_scope_should_be_empty_for_no_projects - assert_equal [], Principal.member_of([]).sort - end - - def test_not_member_of_scope_should_return_users_that_have_no_memberships - projects = Project.find_all_by_id(1, 2) - expected = (Principal.all - projects.map(&:memberships).flatten.map(&:principal)).sort - assert_equal expected, Principal.not_member_of(projects).sort - end - - def test_not_member_of_scope_should_be_empty_for_no_projects - assert_equal [], Principal.not_member_of([]).sort - end - - context "#like" do - setup do - Principal.create!(:login => 'login') - Principal.create!(:login => 'login2') - - Principal.create!(:firstname => 'firstname') - Principal.create!(:firstname => 'firstname2') - - Principal.create!(:lastname => 'lastname') - Principal.create!(:lastname => 'lastname2') - - Principal.create!(:mail => 'mail@example.com') - Principal.create!(:mail => 'mail2@example.com') - - @palmer = Principal.create!(:firstname => 'David', :lastname => 'Palmer') - end - - should "search login" do - results = Principal.like('login') - - assert_equal 2, results.count - assert results.all? {|u| u.login.match(/login/) } - end - - should "search firstname" do - results = Principal.like('firstname') - - assert_equal 2, results.count - assert results.all? {|u| u.firstname.match(/firstname/) } - end - - should "search lastname" do - results = Principal.like('lastname') - - assert_equal 2, results.count - assert results.all? {|u| u.lastname.match(/lastname/) } - end - - should "search mail" do - results = Principal.like('mail') - - 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 - end - - def test_like_scope_with_cyrillic_name - user = User.generate!(:firstname => 'Соболев', :lastname => 'Денис') - results = Principal.like('Собо') - assert_equal 1, results.count - assert_equal user, results.first - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/073a34bd97c7ca9066ab7d9fa3f008e0e0da8c60.svn-base --- a/.svn/pristine/07/073a34bd97c7ca9066ab7d9fa3f008e0e0da8c60.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -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 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/0760e17a4ae9d6716b2c149e62df20d426be1e60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/0760e17a4ae9d6716b2c149e62df20d426be1e60.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,86 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/077e480273d388c124ae7826c285eaf11442be18.svn-base --- a/.svn/pristine/07/077e480273d388c124ae7826c285eaf11442be18.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -<% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> - -
    -<% if @group.memberships.any? %> - - - - - - - - <% @group.memberships.each do |membership| %> - <% next if membership.new_record? %> - - - - - -<% end; reset_cycle %> - -
    <%= l(:label_project) %><%= l(:label_role_plural) %>
    <%=h membership.project %> - <%=h membership.roles.sort.collect(&:to_s).join(', ') %> - <%= form_for(:membership, :remote => true, - :url => { :action => 'edit_membership', :id => @group, :membership_id => membership }, - :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %> -

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

    -

    <%= 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' - ) %> - <%= delete_link({:controller => 'groups', :action => 'destroy_membership', :id => @group, :membership_id => membership}, - :remote => true, - :method => :post) %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    -<% if projects.any? %> -
    <%=l(:label_project_new)%> -<%= form_for(:membership, :remote => true, :url => { :action => 'edit_membership', :id => @group }) do %> -<%= label_tag "membership_project_id", l(:description_choose_project), :class => "hidden-for-sighted" %> -<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %> -

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

    -

    <%= submit_tag l(:button_add) %>

    -<% end %> -
    -<% end %> -
    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/07/0781c86b247e4cfc1df644f5ac2c5e1a6fb8ca1d.svn-base --- a/.svn/pristine/07/0781c86b247e4cfc1df644f5ac2c5e1a6fb8ca1d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2254 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, - :groups_users, - :trackers, :projects_trackers, - :enabled_modules, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, :journals, :journal_details, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - include Redmine::I18n - - def teardown - 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, - :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? - assert_nil issue.estimated_hours - end - - def test_start_date_format_should_be_validated - set_language_if_valid 'en' - ['2012', 'ABC', '2012-15-20'].each do |invalid_date| - issue = Issue.new(:start_date => invalid_date) - assert !issue.valid? - assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}" - end - end - - def test_due_date_format_should_be_validated - set_language_if_valid 'en' - ['2012', 'ABC', '2012-15-20'].each do |invalid_date| - issue = Issue.new(:due_date => invalid_date) - assert !issue.valid? - assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}" - end - end - - def test_due_date_lesser_than_start_date_should_not_validate - set_language_if_valid 'en' - issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02') - assert !issue.valid? - 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') - 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 ["Database can't be blank"], issue.errors.full_messages - # Blank value - issue.custom_field_values = { field.id => '' } - assert !issue.save - assert_equal ["Database can't be blank"], issue.errors.full_messages - # Invalid value - issue.custom_field_values = { field.id => 'SQLServer' } - assert !issue.save - assert_equal ["Database is not included in the list"], issue.errors.full_messages - # 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 test_create_with_parent_issue_id - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :author_id => 1, :subject => 'Group assignment', - :parent_issue_id => 1) - assert_save issue - assert_equal 1, issue.parent_issue_id - assert_equal Issue.find(1), issue.parent - end - - def test_create_with_sharp_parent_issue_id - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :author_id => 1, :subject => 'Group assignment', - :parent_issue_id => "#1") - assert_save issue - assert_equal 1, issue.parent_issue_id - assert_equal Issue.find(1), issue.parent - end - - def test_create_with_invalid_parent_issue_id - set_language_if_valid 'en' - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :author_id => 1, :subject => 'Group assignment', - :parent_issue_id => '01ABC') - assert !issue.save - assert_equal '01ABC', issue.parent_issue_id - assert_include 'Parent task is invalid', issue.errors.full_messages - end - - def test_create_with_invalid_sharp_parent_issue_id - set_language_if_valid 'en' - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :author_id => 1, :subject => 'Group assignment', - :parent_issue_id => '#01ABC') - assert !issue.save - assert_equal '#01ABC', issue.parent_issue_id - assert_include 'Parent task is invalid', issue.errors.full_messages - 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_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_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default - assert Role.anonymous.update_attribute(:issues_visibility, 'default') - issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true) - assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first - assert !issue.visible?(User.anonymous) - end - - def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own - assert Role.anonymous.update_attribute(:issues_visibility, 'own') - issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true) - assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first - assert !issue.visible?(User.anonymous) - 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_open_scope - issues = Issue.open.all - assert_nil issues.detect(&:closed?) - end - - def test_open_scope_with_arg - issues = Issue.open(false).all - 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') - - 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.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :subject => 'Test', - :custom_field_values => {'2' => 'Test'}) - 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_project_and_tracker_first - seq = sequence('seq') - issue = Issue.new - issue.expects(:project_id=).in_sequence(seq) - issue.expects(:tracker_id=).in_sequence(seq) - issue.expects(:subject=).in_sequence(seq) - issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'} - end - - def test_assigning_tracker_and_custom_fields_should_assign_custom_fields - 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_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) - - 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_equal [], 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 - WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, - :old_status_id => 1, :new_status_id => 2, - :author => false, :assignee => false) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, - :old_status_id => 1, :new_status_id => 3, - :author => true, :assignee => false) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, - :old_status_id => 1, :new_status_id => 4, - :author => false, :assignee => true) - WorkflowTransition.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, :author_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, :author_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) - - group = Group.generate! - group.users << user - issue = Issue.generate!(:tracker => tracker, :status => status, - :project_id => 1, :author => user, - :assigned_to => group) - assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) - end - - def test_new_statuses_allowed_to_should_consider_group_assignment - WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, - :old_status_id => 1, :new_status_id => 4, - :author => false, :assignee => true) - user = User.find(2) - group = Group.generate! - group.users << user - - issue = Issue.generate!(:author_id => 1, :assigned_to => group) - assert_include 4, issue.new_statuses_allowed_to(user).map(&:id) - end - - def test_new_statuses_allowed_to_should_return_all_transitions_for_admin - admin = User.find(1) - issue = Issue.find(1) - assert !admin.member_of?(issue.project) - expected_statuses = [issue.status] + - WorkflowTransition.find_all_by_old_status_id( - issue.status_id).map(&:new_status).uniq.sort - assert_equal expected_statuses, issue.new_statuses_allowed_to(admin) - end - - def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying - issue = Issue.find(1).copy - assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id) - - issue = Issue.find(2).copy - assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id) - end - - def test_safe_attributes_names_should_not_include_disabled_field - tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id)) - - issue = Issue.new(:tracker => tracker) - assert_include 'tracker_id', issue.safe_attribute_names - assert_include 'status_id', issue.safe_attribute_names - assert_include 'subject', issue.safe_attribute_names - assert_include 'description', issue.safe_attribute_names - assert_include 'custom_field_values', issue.safe_attribute_names - assert_include 'custom_fields', issue.safe_attribute_names - assert_include 'lock_version', issue.safe_attribute_names - - tracker.core_fields.each do |field| - assert_include field, issue.safe_attribute_names - end - - tracker.disabled_core_fields.each do |field| - assert_not_include field, issue.safe_attribute_names - end - end - - def test_safe_attributes_should_ignore_disabled_fields - tracker = Tracker.find(1) - tracker.core_fields = %w(assigned_to_id due_date) - tracker.save! - - issue = Issue.new(:tracker => tracker) - issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'} - assert_nil issue.start_date - assert_equal Date.parse('2012-07-14'), issue.due_date - end - - def test_safe_attributes_should_accept_target_tracker_enabled_fields - source = Tracker.find(1) - source.core_fields = [] - source.save! - target = Tracker.find(2) - target.core_fields = %w(assigned_to_id due_date) - target.save! - - issue = Issue.new(:tracker => source) - issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'} - assert_equal target, issue.tracker - assert_equal Date.parse('2012-07-14'), issue.due_date - end - - def test_safe_attributes_should_not_include_readonly_fields - WorkflowPermission.delete_all - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 1, :field_name => 'due_date', - :rule => 'readonly') - user = User.find(2) - - issue = Issue.new(:project_id => 1, :tracker_id => 1) - assert_equal %w(due_date), issue.read_only_attribute_names(user) - assert_not_include 'due_date', issue.safe_attribute_names(user) - - issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user - assert_equal Date.parse('2012-07-14'), issue.start_date - assert_nil issue.due_date - end - - def test_safe_attributes_should_not_include_readonly_custom_fields - cf1 = IssueCustomField.create!(:name => 'Writable field', - :field_format => 'string', - :is_for_all => true, :tracker_ids => [1]) - cf2 = IssueCustomField.create!(:name => 'Readonly field', - :field_format => 'string', - :is_for_all => true, :tracker_ids => [1]) - WorkflowPermission.delete_all - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 1, :field_name => cf2.id.to_s, - :rule => 'readonly') - user = User.find(2) - issue = Issue.new(:project_id => 1, :tracker_id => 1) - assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user) - assert_not_include cf2.id.to_s, issue.safe_attribute_names(user) - - issue.send :safe_attributes=, {'custom_field_values' => { - cf1.id.to_s => 'value1', cf2.id.to_s => 'value2' - }}, user - assert_equal 'value1', issue.custom_field_value(cf1) - assert_nil issue.custom_field_value(cf2) - - issue.send :safe_attributes=, {'custom_fields' => [ - {'id' => cf1.id.to_s, 'value' => 'valuea'}, - {'id' => cf2.id.to_s, 'value' => 'valueb'} - ]}, user - assert_equal 'valuea', issue.custom_field_value(cf1) - assert_nil issue.custom_field_value(cf2) - end - - def test_editable_custom_field_values_should_return_non_readonly_custom_values - cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string', - :is_for_all => true, :tracker_ids => [1, 2]) - cf2 = IssueCustomField.create!(:name => 'Readonly field', :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 => cf2.id.to_s, :rule => 'readonly') - user = User.find(2) - - issue = Issue.new(:project_id => 1, :tracker_id => 1) - values = issue.editable_custom_field_values(user) - assert values.detect {|value| value.custom_field == cf1} - assert_nil values.detect {|value| value.custom_field == cf2} - - issue.tracker_id = 2 - values = issue.editable_custom_field_values(user) - assert values.detect {|value| value.custom_field == cf1} - assert values.detect {|value| value.custom_field == cf2} - end - - def test_safe_attributes_should_accept_target_tracker_writable_fields - 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 => 2, - :role_id => 1, :field_name => 'start_date', - :rule => 'readonly') - user = User.find(2) - - issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) - - issue.send :safe_attributes=, {'start_date' => '2012-07-12', - 'due_date' => '2012-07-14'}, user - assert_equal Date.parse('2012-07-12'), issue.start_date - assert_nil issue.due_date - - issue.send :safe_attributes=, {'start_date' => '2012-07-15', - 'due_date' => '2012-07-16', - 'tracker_id' => 2}, user - assert_equal Date.parse('2012-07-12'), issue.start_date - assert_equal Date.parse('2012-07-16'), issue.due_date - end - - def test_safe_attributes_should_accept_target_status_writable_fields - 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 => 2, :tracker_id => 1, - :role_id => 1, :field_name => 'start_date', - :rule => 'readonly') - user = User.find(2) - - issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) - - issue.send :safe_attributes=, {'start_date' => '2012-07-12', - 'due_date' => '2012-07-14'}, - user - assert_equal Date.parse('2012-07-12'), issue.start_date - assert_nil issue.due_date - - issue.send :safe_attributes=, {'start_date' => '2012-07-15', - 'due_date' => '2012-07-16', - 'status_id' => 2}, - user - assert_equal Date.parse('2012-07-12'), issue.start_date - assert_equal Date.parse('2012-07-16'), issue.due_date - end - - def test_required_attributes_should_be_validated - cf = IssueCustomField.create!(:name => 'Foo', :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 => 'category_id', - :rule => 'required') - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 1, :field_name => cf.id.to_s, - :rule => 'required') - - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, - :role_id => 1, :field_name => 'start_date', - :rule => 'required') - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, - :role_id => 1, :field_name => cf.id.to_s, - :rule => 'required') - user = User.find(2) - - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :status_id => 1, :subject => 'Required fields', - :author => user) - assert_equal [cf.id.to_s, "category_id", "due_date"], - issue.required_attribute_names(user).sort - assert !issue.save, "Issue was saved" - assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"], - issue.errors.full_messages.sort - - issue.tracker_id = 2 - assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort - assert !issue.save, "Issue was saved" - assert_equal ["Foo can't be blank", "Start date can't be blank"], - issue.errors.full_messages.sort - - issue.start_date = Date.today - issue.custom_field_values = {cf.id.to_s => 'bar'} - assert issue.save - end - - def test_required_attribute_names_for_multiple_roles_should_intersect_rules - 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 => 'start_date', - :rule => 'required') - user = User.find(2) - member = Member.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) - - assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort - - member.role_ids = [1, 2] - member.save! - assert_equal [], issue.required_attribute_names(user.reload) - - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 2, :field_name => 'due_date', - :rule => 'required') - assert_equal %w(due_date), issue.required_attribute_names(user) - - member.role_ids = [1, 2, 3] - member.save! - assert_equal [], issue.required_attribute_names(user.reload) - - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 2, :field_name => 'due_date', - :rule => 'readonly') - # required + readonly => required - assert_equal %w(due_date), issue.required_attribute_names(user) - end - - def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules - 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 => 'start_date', - :rule => 'readonly') - user = User.find(2) - member = Member.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1) - - assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort - - member.role_ids = [1, 2] - member.save! - assert_equal [], issue.read_only_attribute_names(user.reload) - - WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, - :role_id => 2, :field_name => 'due_date', - :rule => 'readonly') - assert_equal %w(due_date), issue.read_only_attribute_names(user) - end - - def test_copy - issue = Issue.new.copy_from(1) - assert issue.copy? - 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_copy_should_add_relation_with_copied_issue - copied = Issue.find(1) - issue = Issue.new.copy_from(copied) - assert issue.save - issue.reload - - assert_equal 1, issue.relations.size - relation = issue.relations.first - assert_equal 'copied_to', relation.relation_type - assert_equal copied, relation.issue_from - assert_equal issue, relation.issue_to - end - - def test_copy_should_copy_subtasks - issue = Issue.generate_with_descendants! - - copy = issue.reload.copy - copy.author = User.find(7) - assert_difference 'Issue.count', 1+issue.descendants.count do - assert copy.save - end - copy.reload - assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort - child_copy = copy.children.detect {|c| c.subject == 'Child1'} - assert_equal %w(Child11), child_copy.children.map(&:subject).sort - 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! - - copy = issue.copy(:project_id => 3) - assert_difference 'Issue.count', 1+issue.descendants.count do - assert copy.save - end - assert_equal [3], copy.reload.descendants.map(&:project_id).uniq - end - - def test_copy_should_not_copy_subtasks_twice_when_saving_twice - issue = Issue.generate_with_descendants! - - copy = issue.reload.copy - assert_difference 'Issue.count', 1+issue.descendants.count do - assert copy.save - assert copy.save - end - end - - def test_should_not_call_after_project_change_on_creation - issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, - :subject => 'Test', :author_id => 1) - issue.expects(:after_project_change).never - issue.save! - end - - def test_should_not_call_after_project_change_on_update - issue = Issue.find(1) - issue.project = Project.find(1) - issue.subject = 'No project change' - issue.expects(:after_project_change).never - issue.save! - end - - def test_should_call_after_project_change_on_project_change - issue = Issue.find(1) - issue.project = Project.find(2) - issue.expects(:after_project_change).once - issue.save! - end - - def test_adding_journal_should_update_timestamp - issue = Issue.find(1) - updated_on_was = issue.updated_on - - issue.init_journal(User.first, "Adding notes") - assert_difference 'Journal.count' do - assert issue.save - end - issue.reload - - assert_not_equal updated_on_was, issue.updated_on - end - - def test_should_close_duplicates - # Create 3 issues - issue1 = Issue.generate! - issue2 = Issue.generate! - issue3 = Issue.generate! - - # 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.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? - assert issue3.reload.closed? - end - - def test_should_not_close_duplicated_issue - issue1 = Issue.generate! - issue2 = Issue.generate! - - # 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.first, "Closing issue2") - issue2.status = IssueStatus.where(:is_closed => true).first - 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_equal [], 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_equal [], 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_equal [], 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_should_not_be_able_to_keep_unshared_version_when_changing_project - issue = Issue.find(2) - assert_equal 2, issue.fixed_version_id - issue.project_id = 3 - assert_nil issue.fixed_version_id - issue.fixed_version_id = 2 - assert !issue.save - assert_include 'Target version is not included in the list', issue.errors.full_messages - end - - def test_should_keep_shared_version_when_changing_project - Version.find(2).update_attribute :sharing, 'tree' - - issue = Issue.find(2) - assert_equal 2, issue.fixed_version_id - issue.project_id = 3 - assert_equal 2, issue.fixed_version_id - assert issue.save - end - - def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled - assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2)) - end - - def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled - Project.find(2).disable_module! :issue_tracking - assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2)) - end - - def test_move_to_another_project_with_same_category - issue = Issue.find(1) - issue.project = Project.find(2) - assert issue.save - 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) - issue.project = Project.find(2) - assert issue.save - 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) - issue.project = Project.find(2) - assert issue.save - 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) - issue.project = Project.find(5) - assert issue.save - 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) - issue.project = Project.find(5) - assert issue.save - 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) - issue.project = Project.find(2) - assert issue.save - 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_should_keep_parent_if_valid - issue = Issue.find(1) - issue.update_attribute(:parent_issue_id, 2) - issue.project = Project.find(3) - assert issue.save - issue.reload - assert_equal 2, issue.parent_id - end - - def test_move_to_another_project_should_clear_parent_if_not_valid - issue = Issue.find(1) - issue.update_attribute(:parent_issue_id, 2) - issue.project = Project.find(2) - assert issue.save - issue.reload - assert_nil issue.parent_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 - issue.project = target - assert issue.save - issue.reload - assert_equal 2, issue.project_id - assert_equal 3, issue.tracker_id - end - - def test_copy_to_the_same_project - issue = Issue.find(1) - copy = issue.copy - assert_difference 'Issue.count' do - copy.save! - 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 = issue.copy(:project_id => 3, :tracker_id => 2) - assert_difference 'Issue.count' do - copy.save! - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - - 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! - - 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 - issue = Issue.find(1) - issue_in_same_project = Issue.find(2) - issue_in_child_project = Issue.find(5) - issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1) - issue_in_other_child_project = Issue.find(6) - issue_in_different_tree = Issue.find(4) - - with_settings :cross_project_subtasks => '' do - assert_equal true, issue.valid_parent_project?(issue_in_same_project) - assert_equal false, issue.valid_parent_project?(issue_in_child_project) - assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project) - assert_equal false, issue.valid_parent_project?(issue_in_different_tree) - end - - with_settings :cross_project_subtasks => 'system' do - assert_equal true, issue.valid_parent_project?(issue_in_same_project) - assert_equal true, issue.valid_parent_project?(issue_in_child_project) - assert_equal true, issue.valid_parent_project?(issue_in_different_tree) - end - - with_settings :cross_project_subtasks => 'tree' do - assert_equal true, issue.valid_parent_project?(issue_in_same_project) - assert_equal true, issue.valid_parent_project?(issue_in_child_project) - assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project) - assert_equal false, issue.valid_parent_project?(issue_in_different_tree) - - assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project) - assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project) - end - - with_settings :cross_project_subtasks => 'descendants' do - assert_equal true, issue.valid_parent_project?(issue_in_same_project) - assert_equal false, issue.valid_parent_project?(issue_in_child_project) - assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project) - assert_equal false, issue.valid_parent_project?(issue_in_different_tree) - - assert_equal true, issue_in_child_project.valid_parent_project?(issue) - assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project) - end - end - - def test_recipients_should_include_previous_assignee - user = User.find(3) - user.members.update_all ["mail_notification = ?", false] - user.update_attribute :mail_notification, 'only_assigned' - - issue = Issue.find(2) - issue.assigned_to = nil - assert_include user.mail, issue.recipients - issue.save! - assert !issue.recipients.include?(user.mail) - end - - def test_recipients_should_not_include_users_that_cannot_view_the_issue - issue = Issue.find(12) - assert issue.recipients.include?(issue.author.mail) - # copy the issue to a private project - copy = issue.copy(:project_id => 5, :tracker_id => 2) - # 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! - 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_destroying_a_deleted_issue_should_not_raise_an_error - issue = Issue.find(1) - Issue.find(1).destroy - - assert_nothing_raised do - assert_no_difference 'Issue.count' do - issue.destroy - end - assert issue.destroyed? - end - end - - def test_destroying_a_stale_issue_should_not_raise_an_error - issue = Issue.find(1) - Issue.find(1).update_attribute :subject, "Updated" - - assert_nothing_raised do - assert_difference 'Issue.count', -1 do - issue.destroy - end - assert issue.destroyed? - end - 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_reschedule_an_issue_without_dates - with_settings :non_working_week_days => [] do - issue = Issue.new(:start_date => nil, :due_date => nil) - issue.reschedule_on '2012-10-09'.to_date - assert_equal '2012-10-09'.to_date, issue.start_date - assert_equal '2012-10-09'.to_date, issue.due_date - end - - with_settings :non_working_week_days => %w(6 7) do - issue = Issue.new(:start_date => nil, :due_date => nil) - issue.reschedule_on '2012-10-09'.to_date - assert_equal '2012-10-09'.to_date, issue.start_date - assert_equal '2012-10-09'.to_date, issue.due_date - - issue = Issue.new(:start_date => nil, :due_date => nil) - issue.reschedule_on '2012-10-13'.to_date - assert_equal '2012-10-15'.to_date, issue.start_date - assert_equal '2012-10-15'.to_date, issue.due_date - end - end - - def test_reschedule_an_issue_with_start_date - with_settings :non_working_week_days => [] do - issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) - issue.reschedule_on '2012-10-13'.to_date - assert_equal '2012-10-13'.to_date, issue.start_date - assert_equal '2012-10-13'.to_date, issue.due_date - end - - with_settings :non_working_week_days => %w(6 7) do - issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) - issue.reschedule_on '2012-10-11'.to_date - assert_equal '2012-10-11'.to_date, issue.start_date - assert_equal '2012-10-11'.to_date, issue.due_date - - issue = Issue.new(:start_date => '2012-10-09', :due_date => nil) - issue.reschedule_on '2012-10-13'.to_date - assert_equal '2012-10-15'.to_date, issue.start_date - assert_equal '2012-10-15'.to_date, issue.due_date - end - end - - def test_reschedule_an_issue_with_start_and_due_dates - with_settings :non_working_week_days => [] do - issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15') - issue.reschedule_on '2012-10-13'.to_date - assert_equal '2012-10-13'.to_date, issue.start_date - assert_equal '2012-10-19'.to_date, issue.due_date - end - - with_settings :non_working_week_days => %w(6 7) do - issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days - issue.reschedule_on '2012-10-11'.to_date - assert_equal '2012-10-11'.to_date, issue.start_date - assert_equal '2012-10-23'.to_date, issue.due_date - - issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') - issue.reschedule_on '2012-10-13'.to_date - assert_equal '2012-10-15'.to_date, issue.start_date - assert_equal '2012-10-25'.to_date, issue.due_date - end - end - - def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue - issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, - :relation_type => IssueRelation::TYPE_PRECEDES) - assert_equal Date.parse('2012-10-18'), issue2.reload.start_date - - issue1.due_date = '2012-10-23' - issue1.save! - issue2.reload - assert_equal Date.parse('2012-10-24'), issue2.start_date - assert_equal Date.parse('2012-10-26'), issue2.due_date - end - - def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue - issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, - :relation_type => IssueRelation::TYPE_PRECEDES) - assert_equal Date.parse('2012-10-18'), issue2.reload.start_date - - issue1.start_date = '2012-09-17' - issue1.due_date = '2012-09-18' - issue1.save! - issue2.reload - assert_equal Date.parse('2012-09-19'), issue2.start_date - assert_equal Date.parse('2012-09-21'), issue2.due_date - end - - def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues - issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17') - issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02') - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, - :relation_type => IssueRelation::TYPE_PRECEDES) - IssueRelation.create!(:issue_from => issue3, :issue_to => issue2, - :relation_type => IssueRelation::TYPE_PRECEDES) - assert_equal Date.parse('2012-10-18'), issue2.reload.start_date - - issue1.start_date = '2012-09-17' - issue1.due_date = '2012-09-18' - issue1.save! - issue2.reload - # Issue 2 must start after Issue 3 - assert_equal Date.parse('2012-10-03'), issue2.start_date - assert_equal Date.parse('2012-10-05'), issue2.due_date - end - - def test_rescheduling_a_stale_issue_should_not_raise_an_error - with_settings :non_working_week_days => [] do - stale = Issue.find(1) - issue = Issue.find(1) - issue.subject = "Updated" - issue.save! - date = 10.days.from_now.to_date - assert_nothing_raised do - stale.reschedule_on!(date) - end - assert_equal date, stale.reload.start_date - 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.where(:is_closed => true).first - ).overdue? - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - - test "#assignable_users without issue_group_assignment 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 - - 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_journalized_multi_custom_field - field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', - :is_filter => true, :is_for_all => true, - :tracker_ids => [1], - :possible_values => ['value1', 'value2', 'value3'], - :multiple => true) - - issue = Issue.create!(:project_id => 1, :tracker_id => 1, - :subject => 'Test', :author_id => 1) - - assert_difference 'Journal.count' do - assert_difference 'JournalDetail.count' do - issue.init_journal(User.first) - issue.custom_field_values = {field.id => ['value1']} - issue.save! - end - assert_difference 'JournalDetail.count' do - issue.init_journal(User.first) - issue.custom_field_values = {field.id => ['value1', 'value2']} - issue.save! - end - assert_difference 'JournalDetail.count', 2 do - issue.init_journal(User.first) - issue.custom_field_values = {field.id => ['value3', 'value2']} - issue.save! - end - assert_difference 'JournalDetail.count', 2 do - issue.init_journal(User.first) - issue.custom_field_values = {field.id => nil} - issue.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.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.where("id <> ?", i.priority_id).first - 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_subtask - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue = Issue.generate!(:project => project) - childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) - childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) - - assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort - end - - def test_all_dependent_issues_does_not_include_self - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue = Issue.generate!(:project => project) - childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) - - assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id) - end - - def test_all_dependent_issues_with_parenttask_and_sibling - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue = Issue.generate!(:project => project) - childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) - childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id) - - assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id) - end - - def test_all_dependent_issues_with_relation_to_leaf_in_other_tree - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue1 = Issue.generate!(:project => project) - childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - - parentIssue2 = Issue.generate!(:project => project) - childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - - - assert IssueRelation.create(:issue_from => parentIssue1, - :issue_to => childIssue2_2, - :relation_type => IssueRelation::TYPE_BLOCKS) - - assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort, - parentIssue1.all_dependent_issues.collect(&:id).uniq.sort - end - - def test_all_dependent_issues_with_relation_to_parent_in_other_tree - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue1 = Issue.generate!(:project => project) - childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - - parentIssue2 = Issue.generate!(:project => project) - childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - - - assert IssueRelation.create(:issue_from => parentIssue1, - :issue_to => parentIssue2, - :relation_type => IssueRelation::TYPE_BLOCKS) - - assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort, - parentIssue1.all_dependent_issues.collect(&:id).uniq.sort - end - - def test_all_dependent_issues_with_transitive_relation - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue1 = Issue.generate!(:project => project) - childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - - parentIssue2 = Issue.generate!(:project => project) - childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - - independentIssue = Issue.generate!(:project => project) - - assert IssueRelation.create(:issue_from => parentIssue1, - :issue_to => childIssue2_1, - :relation_type => IssueRelation::TYPE_RELATES) - - assert IssueRelation.create(:issue_from => childIssue2_1, - :issue_to => independentIssue, - :relation_type => IssueRelation::TYPE_RELATES) - - assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort, - parentIssue1.all_dependent_issues.collect(&:id).uniq.sort - end - - def test_all_dependent_issues_with_transitive_relation2 - IssueRelation.delete_all - - project = Project.generate!(:name => "testproject") - - parentIssue1 = Issue.generate!(:project => project) - childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id) - - parentIssue2 = Issue.generate!(:project => project) - childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id) - - independentIssue = Issue.generate!(:project => project) - - assert IssueRelation.create(:issue_from => parentIssue1, - :issue_to => independentIssue, - :relation_type => IssueRelation::TYPE_RELATES) - - assert IssueRelation.create(:issue_from => independentIssue, - :issue_to => childIssue2_1, - :relation_type => IssueRelation::TYPE_RELATES) - - assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort, - parentIssue1.all_dependent_issues.collect(&:id).uniq.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) - - r = IssueRelation.create!(:issue_from => Issue.find(3), - :issue_to => Issue.find(7), - :relation_type => IssueRelation::TYPE_PRECEDES) - IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id]) - - 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) - - r = IssueRelation.create!(:issue_from => Issue.find(8), - :issue_to => Issue.find(7), - :relation_type => IssueRelation::TYPE_RELATES) - IssueRelation.update_all("issue_to_id = 2", ["id = ?", r.id]) - - r = IssueRelation.create!(:issue_from => Issue.find(3), - :issue_to => Issue.find(7), - :relation_type => IssueRelation::TYPE_RELATES) - IssueRelation.update_all("issue_to_id = 1", ["id = ?", r.id]) - - assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort - end - - 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 - - with_settings :issue_done_ratio => 'issue_status' do - assert_equal 50, @issue.done_ratio - assert_equal 0, @issue2.done_ratio - end - end - - 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 - - with_settings :issue_done_ratio => 'issue_status' 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 - - 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 - - def test_recently_updated_scope - #should return the last updated issue - assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.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!(: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 - - 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 - - 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 - - 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 - - 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 - - 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 - - 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 - assert_equal 2, Issue.find(1).last_journal_id - end - - def test_last_journal_id_without_journals_should_return_nil - assert_nil Issue.find(3).last_journal_id - end - - def test_journals_after_should_return_journals_with_greater_id - assert_equal [Journal.find(2)], Issue.find(1).journals_after('1') - assert_equal [], Issue.find(1).journals_after('2') - end - - def test_journals_after_with_blank_arg_should_return_all_journals - 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(' ') - assert_include 'priority-8', classes - assert_include 'priority-highest', classes - end - - def test_save_attachments_with_hash_should_save_attachments_in_keys_order - set_tmp_attachments_directory - issue = Issue.generate! - issue.save_attachments({ - 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')}, - '3' => {'file' => mock_file_with_options(:original_filename => 'bar')}, - '1' => {'file' => mock_file_with_options(:original_filename => 'foo')} - }) - issue.attach_saved_attachments - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/081cb3c9d1cc0a40761d7f17097c046e4db924a7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/081cb3c9d1cc0a40761d7f17097c046e4db924a7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,264 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 => '' + 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 = User.generate!(:firstname => 'Some', :lastname => 'Watcher') + assert_equal 'Some Watcher', user.name + 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 + assert page.has_no_css?('form#new-watcher-form') + assert page.has_no_content?('Some Watcher') + 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 page.has_css?('form#issue-form') + assert page.has_css?('p#watchers_form') + using_wait_time(30) do + within('span#watchers_inputs') do + within("label#issue_watcher_user_ids_#{user.id}") do + assert has_content?('Some Watcher'), "No watcher content" + end + end + 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_create_issue_start_due_date + with_settings :default_issue_start_date_to_creation_date => 0 do + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + assert_equal "", page.find('input#issue_start_date').value + assert_equal "", page.find('input#issue_due_date').value + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_start_date').value + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal Date.today.to_s, page.find('input#issue_due_date').value + end + end + + def test_create_issue_start_due_date_default + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Start date', :with => '2012-04-01' + fill_in 'Due date', :with => '' + page.first('p#due_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_due_date').value + + fill_in 'Start date', :with => '' + fill_in 'Due date', :with => '2012-04-01' + page.first('p#start_date_area img').click + page.first("td.ui-datepicker-days-cell-over a").click + assert_equal '2012-04-01', page.find('input#issue_start_date').value + 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 + assert page.first('#sidebar').has_content?('Watchers (0)') + end + assert page.first('#sidebar').has_no_content?(user.name) + end + + def test_watch_issue_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + assert page.has_css?('tr#issue-1') + 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 + assert page.has_css?('tr#issue-1') + 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' + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + 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 + assert page.has_css?('tr#issue-1') + assert page.has_css?('tr#issue-4') + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08415c997f1d03456d84b86652b037399f12d911.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/08415c997f1d03456d84b86652b037399f12d911.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 LayoutTest < ActionController::IntegrationTest + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + test "browsing to a missing page should render the base layout" do + get "/users/100000000" + + assert_response :not_found + + # UsersController uses the admin layout by default + assert_select "#admin-menu", :count => 0 + end + + test "browsing to an unauthorized page should render the base layout" do + change_user_password('miscuser9', 'test1234') + + log_user('miscuser9','test1234') + + get "/admin" + assert_response :forbidden + assert_select "#admin-menu", :count => 0 + end + + def test_top_menu_and_search_not_visible_when_login_required + with_settings :login_required => '1' do + get '/' + assert_select "#top-menu > ul", 0 + assert_select "#quick-search", 0 + end + end + + def test_top_menu_and_search_visible_when_login_not_required + with_settings :login_required => '0' do + get '/' + assert_select "#top-menu > ul" + assert_select "#quick-search" + end + end + + def test_wiki_formatter_header_tags + Role.anonymous.add_permission! :add_issues + + get '/projects/ecookbook/issues/new' + assert_tag :script, + :attributes => {:src => %r{^/javascripts/jstoolbar/jstoolbar-textile.min.js}}, + :parent => {:tag => 'head'} + end + + def test_calendar_header_tags + with_settings :default_language => 'fr' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-fr.js", response.body + end + + with_settings :default_language => 'en-GB' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-en-GB.js", response.body + end + + with_settings :default_language => 'en' do + 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 + get '/' + assert_select 'div#quick-search form[action=/search]' + end + + def test_search_field_inside_project_should_link_to_project_search + get '/projects/ecookbook' + assert_select 'div#quick-search form[action=/projects/ecookbook/search]' + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/084dba3511eace99e8953d1d7626d2fa107595d7.svn-base --- a/.svn/pristine/08/084dba3511eace99e8953d1d7626d2fa107595d7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 self, @news_count, @limit, params['page'] - @offset ||= @news_pages.current.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 :controller => 'news', :action => 'index', :project_id => @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 :action => 'show', :id => @news - else - render :action => 'edit' - end - end - - def destroy - @news.destroy - redirect_to :action => 'index', :project_id => @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/085f61120574ccfad3bedefd8a6623102e2bf32b.svn-base --- a/.svn/pristine/08/085f61120574ccfad3bedefd8a6623102e2bf32b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -<% content_for :header_tags do %> - <%= javascript_include_tag 'repository_navigation' %> -<% end %> - -<%= link_to l(:label_statistics), - {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param}, - :class => 'icon icon-stats' if @repository.supports_all_revisions? %> - -<%= form_tag({:action => controller.action_name, - :id => @project, - :repository_id => @repository.identifier_param, - :path => to_path_param(@path), - :rev => nil}, - {:method => :get, :id => 'revision_selector'}) do -%> - - <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> - | <%= l(:label_branch) %>: - <%= select_tag :branch, - options_for_select([''] + @repository.branches, @rev), - :id => 'branch' %> - <% end -%> - - <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> - | <%= l(:label_tag) %>: - <%= select_tag :tag, - options_for_select([''] + @repository.tags, @rev), - :id => 'tag' %> - <% end -%> - - <% if @repository.supports_all_revisions? %> - | <%= l(:label_revision) %>: - <%= text_field_tag 'rev', @rev, :size => 8 %> - <% end %> -<% end -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08ad4f57c782749abd613605456ebd0acf953d61.svn-base --- a/.svn/pristine/08/08ad4f57c782749abd613605456ebd0acf953d61.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,978 +0,0 @@ -begin - require 'zlib' - @@__have_zlib = true -rescue - @@__have_zlib = false -end - -require 'rexml/document' - -module SVG - module Graph - VERSION = '@ANT_VERSION@' - - # === Base object for generating SVG Graphs - # - # == Synopsis - # - # This class is only used as a superclass of specialized charts. Do not - # attempt to use this class directly, unless creating a new chart type. - # - # For examples of how to subclass this class, see the existing specific - # subclasses, such as SVG::Graph::Pie. - # - # == Examples - # - # For examples of how to use this package, see either the test files, or - # the documentation for the specific class you want to use. - # - # * file:test/plot.rb - # * file:test/single.rb - # * file:test/test.rb - # * file:test/timeseries.rb - # - # == Description - # - # This package should be used as a base for creating SVG graphs. - # - # == Acknowledgements - # - # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby - # port is based on. - # - # Stephen Morgan for creating the TT template and SVG. - # - # == See - # - # * SVG::Graph::BarHorizontal - # * SVG::Graph::Bar - # * SVG::Graph::Line - # * SVG::Graph::Pie - # * SVG::Graph::Plot - # * SVG::Graph::TimeSeries - # - # == Author - # - # Sean E. Russell - # - # Copyright 2004 Sean E. Russell - # This software is available under the Ruby license[LICENSE.txt] - # - class Graph - include REXML - - # Initialize the graph object with the graph settings. You won't - # instantiate this class directly; see the subclass for options. - # [width] 500 - # [height] 300 - # [show_x_guidelines] false - # [show_y_guidelines] true - # [show_data_values] true - # [min_scale_value] 0 - # [show_x_labels] true - # [stagger_x_labels] false - # [rotate_x_labels] false - # [step_x_labels] 1 - # [step_include_first_x_label] true - # [show_y_labels] true - # [rotate_y_labels] false - # [scale_integers] false - # [show_x_title] false - # [x_title] 'X Field names' - # [show_y_title] false - # [y_title_text_direction] :bt - # [y_title] 'Y Scale' - # [show_graph_title] false - # [graph_title] 'Graph Title' - # [show_graph_subtitle] false - # [graph_subtitle] 'Graph Sub Title' - # [key] true, - # [key_position] :right, # bottom or righ - # [font_size] 12 - # [title_font_size] 16 - # [subtitle_font_size] 14 - # [x_label_font_size] 12 - # [x_title_font_size] 14 - # [y_label_font_size] 12 - # [y_title_font_size] 14 - # [key_font_size] 10 - # [no_css] false - # [add_popups] false - def initialize( config ) - @config = config - - self.top_align = self.top_font = self.right_align = self.right_font = 0 - - init_with({ - :width => 500, - :height => 300, - :show_x_guidelines => false, - :show_y_guidelines => true, - :show_data_values => true, - -# :min_scale_value => 0, - - :show_x_labels => true, - :stagger_x_labels => false, - :rotate_x_labels => false, - :step_x_labels => 1, - :step_include_first_x_label => true, - - :show_y_labels => true, - :rotate_y_labels => false, - :stagger_y_labels => false, - :scale_integers => false, - - :show_x_title => false, - :x_title => 'X Field names', - - :show_y_title => false, - :y_title_text_direction => :bt, - :y_title => 'Y Scale', - - :show_graph_title => false, - :graph_title => 'Graph Title', - :show_graph_subtitle => false, - :graph_subtitle => 'Graph Sub Title', - :key => true, - :key_position => :right, # bottom or right - - :font_size =>12, - :title_font_size =>16, - :subtitle_font_size =>14, - :x_label_font_size =>12, - :x_title_font_size =>14, - :y_label_font_size =>12, - :y_title_font_size =>14, - :key_font_size =>10, - - :no_css =>false, - :add_popups =>false, - }) - - set_defaults if respond_to? :set_defaults - - init_with config - end - - - # This method allows you do add data to the graph object. - # It can be called several times to add more data sets in. - # - # data_sales_02 = [12, 45, 21]; - # - # graph.add_data({ - # :data => data_sales_02, - # :title => 'Sales 2002' - # }) - def add_data conf - @data = [] unless defined? @data - - if conf[:data] and conf[:data].kind_of? Array - @data << conf - else - raise "No data provided by #{conf.inspect}" - end - end - - - # This method removes all data from the object so that you can - # reuse it to create a new graph but with the same config options. - # - # graph.clear_data - def clear_data - @data = [] - end - - - # This method processes the template with the data and - # config which has been set and returns the resulting SVG. - # - # This method will croak unless at least one data set has - # been added to the graph object. - # - # print graph.burn - def burn - raise "No data available" unless @data.size > 0 - - calculations if respond_to? :calculations - - start_svg - calculate_graph_dimensions - @foreground = Element.new( "g" ) - draw_graph - draw_titles - draw_legend - draw_data - @graph.add_element( @foreground ) - style - - data = "" - @doc.write( data, 0 ) - - if @config[:compress] - if @@__have_zlib - inp, out = IO.pipe - gz = Zlib::GzipWriter.new( out ) - gz.write data - gz.close - data = inp.read - else - data << ""; - end - end - - return data - end - - - # Set the height of the graph box, this is the total height - # of the SVG box created - not the graph it self which auto - # scales to fix the space. - attr_accessor :height - # Set the width of the graph box, this is the total width - # of the SVG box created - not the graph it self which auto - # scales to fix the space. - attr_accessor :width - # Set the path to an external stylesheet, set to '' if - # you want to revert back to using the defaut internal version. - # - # To create an external stylesheet create a graph using the - # default internal version and copy the stylesheet section to - # an external file and edit from there. - attr_accessor :style_sheet - # (Bool) Show the value of each element of data on the graph - attr_accessor :show_data_values - # The point at which the Y axis starts, defaults to '0', - # if set to nil it will default to the minimum data value. - attr_accessor :min_scale_value - # Whether to show labels on the X axis or not, defaults - # to true, set to false if you want to turn them off. - attr_accessor :show_x_labels - # This puts the X labels at alternative levels so if they - # are long field names they will not overlap so easily. - # Default it false, to turn on set to true. - attr_accessor :stagger_x_labels - # This puts the Y labels at alternative levels so if they - # are long field names they will not overlap so easily. - # Default it false, to turn on set to true. - attr_accessor :stagger_y_labels - # This turns the X axis labels by 90 degrees. - # Default it false, to turn on set to true. - attr_accessor :rotate_x_labels - # This turns the Y axis labels by 90 degrees. - # Default it false, to turn on set to true. - attr_accessor :rotate_y_labels - # How many "steps" to use between displayed X axis labels, - # a step of one means display every label, a step of two results - # in every other label being displayed (label label label), - # a step of three results in every third label being displayed - # (label label label) and so on. - attr_accessor :step_x_labels - # Whether to (when taking "steps" between X axis labels) step from - # the first label (i.e. always include the first label) or step from - # the X axis origin (i.e. start with a gap if step_x_labels is greater - # than one). - attr_accessor :step_include_first_x_label - # Whether to show labels on the Y axis or not, defaults - # to true, set to false if you want to turn them off. - attr_accessor :show_y_labels - # Ensures only whole numbers are used as the scale divisions. - # Default it false, to turn on set to true. This has no effect if - # scale divisions are less than 1. - attr_accessor :scale_integers - # This defines the gap between markers on the Y axis, - # default is a 10th of the max_value, e.g. you will have - # 10 markers on the Y axis. NOTE: do not set this too - # low - you are limited to 999 markers, after that the - # graph won't generate. - attr_accessor :scale_divisions - # Whether to show the title under the X axis labels, - # default is false, set to true to show. - attr_accessor :show_x_title - # What the title under X axis should be, e.g. 'Months'. - attr_accessor :x_title - # Whether to show the title under the Y axis labels, - # default is false, set to true to show. - attr_accessor :show_y_title - # Aligns writing mode for Y axis label. - # Defaults to :bt (Bottom to Top). - # Change to :tb (Top to Bottom) to reverse. - attr_accessor :y_title_text_direction - # What the title under Y axis should be, e.g. 'Sales in thousands'. - attr_accessor :y_title - # Whether to show a title on the graph, defaults - # to false, set to true to show. - attr_accessor :show_graph_title - # What the title on the graph should be. - attr_accessor :graph_title - # Whether to show a subtitle on the graph, defaults - # to false, set to true to show. - attr_accessor :show_graph_subtitle - # What the subtitle on the graph should be. - attr_accessor :graph_subtitle - # Whether to show a key, defaults to false, set to - # true if you want to show it. - attr_accessor :key - # Where the key should be positioned, defaults to - # :right, set to :bottom if you want to move it. - attr_accessor :key_position - # Set the font size (in points) of the data point labels - attr_accessor :font_size - # Set the font size of the X axis labels - attr_accessor :x_label_font_size - # Set the font size of the X axis title - attr_accessor :x_title_font_size - # Set the font size of the Y axis labels - attr_accessor :y_label_font_size - # Set the font size of the Y axis title - attr_accessor :y_title_font_size - # Set the title font size - attr_accessor :title_font_size - # Set the subtitle font size - attr_accessor :subtitle_font_size - # Set the key font size - attr_accessor :key_font_size - # Show guidelines for the X axis - attr_accessor :show_x_guidelines - # Show guidelines for the Y axis - attr_accessor :show_y_guidelines - # Do not use CSS if set to true. Many SVG viewers do not support CSS, but - # not using CSS can result in larger SVGs as well as making it impossible to - # change colors after the chart is generated. Defaults to false. - attr_accessor :no_css - # Add popups for the data points on some graphs - attr_accessor :add_popups - - - protected - - def sort( *arrys ) - sort_multiple( arrys ) - end - - # Overwrite configuration options with supplied options. Used - # by subclasses. - def init_with config - config.each { |key, value| - self.send((key.to_s+"=").to_sym, value ) if respond_to? key.to_sym - } - end - - attr_accessor :top_align, :top_font, :right_align, :right_font - - KEY_BOX_SIZE = 12 - - # Override this (and call super) to change the margin to the left - # of the plot area. Results in @border_left being set. - def calculate_left_margin - @border_left = 7 - # Check for Y labels - max_y_label_height_px = rotate_y_labels ? - y_label_font_size : - get_y_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * y_label_font_size * 0.6 - @border_left += max_y_label_height_px if show_y_labels - @border_left += max_y_label_height_px + 10 if stagger_y_labels - @border_left += y_title_font_size + 5 if show_y_title - end - - - # Calculates the width of the widest Y label. This will be the - # character height if the Y labels are rotated - def max_y_label_width_px - return font_size if rotate_y_labels - end - - - # Override this (and call super) to change the margin to the right - # of the plot area. Results in @border_right being set. - def calculate_right_margin - @border_right = 7 - if key and key_position == :right - val = keys.max { |a,b| a.length <=> b.length } - @border_right += val.length * key_font_size * 0.6 - @border_right += KEY_BOX_SIZE - @border_right += 10 # Some padding around the box - end - end - - - # Override this (and call super) to change the margin to the top - # of the plot area. Results in @border_top being set. - def calculate_top_margin - @border_top = 5 - @border_top += title_font_size if show_graph_title - @border_top += 5 - @border_top += subtitle_font_size if show_graph_subtitle - end - - - # Adds pop-up point information to a graph. - def add_popup( x, y, label ) - txt_width = label.length * font_size * 0.6 + 10 - tx = (x+txt_width > width ? x-5 : x+5) - t = @foreground.add_element( "text", { - "x" => tx.to_s, - "y" => (y - font_size).to_s, - "visibility" => "hidden", - }) - t.attributes["style"] = "fill: #000; "+ - (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") - t.text = label.to_s - t.attributes["id"] = t.object_id.to_s - - @foreground.add_element( "circle", { - "cx" => x.to_s, - "cy" => y.to_s, - "r" => "10", - "style" => "opacity: 0", - "onmouseover" => - "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", - "onmouseout" => - "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", - }) - - end - - - # Override this (and call super) to change the margin to the bottom - # of the plot area. Results in @border_bottom being set. - def calculate_bottom_margin - @border_bottom = 7 - if key and key_position == :bottom - @border_bottom += @data.size * (font_size + 5) - @border_bottom += 10 - end - if show_x_labels - max_x_label_height_px = (not rotate_x_labels) ? - x_label_font_size : - get_x_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * x_label_font_size * 0.6 - @border_bottom += max_x_label_height_px - @border_bottom += max_x_label_height_px + 10 if stagger_x_labels - end - @border_bottom += x_title_font_size + 5 if show_x_title - end - - - # Draws the background, axis, and labels. - def draw_graph - @graph = @root.add_element( "g", { - "transform" => "translate( #@border_left #@border_top )" - }) - - # Background - @graph.add_element( "rect", { - "x" => "0", - "y" => "0", - "width" => @graph_width.to_s, - "height" => @graph_height.to_s, - "class" => "graphBackground" - }) - - # Axis - @graph.add_element( "path", { - "d" => "M 0 0 v#@graph_height", - "class" => "axis", - "id" => "xAxis" - }) - @graph.add_element( "path", { - "d" => "M 0 #@graph_height h#@graph_width", - "class" => "axis", - "id" => "yAxis" - }) - - draw_x_labels - draw_y_labels - end - - - # Where in the X area the label is drawn - # Centered in the field, should be width/2. Start, 0. - def x_label_offset( width ) - 0 - end - - def make_datapoint_text( x, y, value, style="" ) - if show_data_values - @foreground.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "dataPointLabel", - "style" => "#{style} stroke: #fff; stroke-width: 2;" - }).text = value.to_s - text = @foreground.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "dataPointLabel" - }) - text.text = value.to_s - text.attributes["style"] = style if style.length > 0 - end - end - - - # Draws the X axis labels - def draw_x_labels - stagger = x_label_font_size + 5 - if show_x_labels - label_width = field_width - - count = 0 - for label in get_x_labels - if step_include_first_x_label == true then - step = count % step_x_labels - else - step = (count + 1) % step_x_labels - end - - if step == 0 then - text = @graph.add_element( "text" ) - text.attributes["class"] = "xAxisLabels" - text.text = label.to_s - - x = count * label_width + x_label_offset( label_width ) - y = @graph_height + x_label_font_size + 3 - t = 0 - (font_size / 2) - - if stagger_x_labels and count % 2 == 1 - y += stagger - @graph.add_element( "path", { - "d" => "M#{x} #@graph_height v#{stagger}", - "class" => "staggerGuideLine" - }) - end - - text.attributes["x"] = x.to_s - text.attributes["y"] = y.to_s - if rotate_x_labels - text.attributes["transform"] = - "rotate( 90 #{x} #{y-x_label_font_size} )"+ - " translate( 0 -#{x_label_font_size/4} )" - text.attributes["style"] = "text-anchor: start" - else - text.attributes["style"] = "text-anchor: middle" - end - end - - draw_x_guidelines( label_width, count ) if show_x_guidelines - count += 1 - end - end - end - - - # Where in the Y area the label is drawn - # Centered in the field, should be width/2. Start, 0. - def y_label_offset( height ) - 0 - end - - - def field_width - (@graph_width.to_f - font_size*2*right_font) / - (get_x_labels.length - right_align) - end - - - def field_height - (@graph_height.to_f - font_size*2*top_font) / - (get_y_labels.length - top_align) - end - - - # Draws the Y axis labels - def draw_y_labels - stagger = y_label_font_size + 5 - if show_y_labels - label_height = field_height - - count = 0 - y_offset = @graph_height + y_label_offset( label_height ) - y_offset += font_size/1.2 unless rotate_y_labels - for label in get_y_labels - y = y_offset - (label_height * count) - x = rotate_y_labels ? 0 : -3 - - if stagger_y_labels and count % 2 == 1 - x -= stagger - @graph.add_element( "path", { - "d" => "M#{x} #{y} h#{stagger}", - "class" => "staggerGuideLine" - }) - end - - text = @graph.add_element( "text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "yAxisLabels" - }) - text.text = label.to_s - if rotate_y_labels - text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ - "rotate( 90 #{x} #{y} ) " - text.attributes["style"] = "text-anchor: middle" - else - text.attributes["y"] = (y - (y_label_font_size/2)).to_s - text.attributes["style"] = "text-anchor: end" - end - draw_y_guidelines( label_height, count ) if show_y_guidelines - count += 1 - end - end - end - - - # Draws the X axis guidelines - def draw_x_guidelines( label_height, count ) - if count != 0 - @graph.add_element( "path", { - "d" => "M#{label_height*count} 0 v#@graph_height", - "class" => "guideLines" - }) - end - end - - - # Draws the Y axis guidelines - def draw_y_guidelines( label_height, count ) - if count != 0 - @graph.add_element( "path", { - "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", - "class" => "guideLines" - }) - end - end - - - # Draws the graph title and subtitle - def draw_titles - if show_graph_title - @root.add_element( "text", { - "x" => (width / 2).to_s, - "y" => (title_font_size).to_s, - "class" => "mainTitle" - }).text = graph_title.to_s - end - - if show_graph_subtitle - y_subtitle = show_graph_title ? - title_font_size + 10 : - subtitle_font_size - @root.add_element("text", { - "x" => (width / 2).to_s, - "y" => (y_subtitle).to_s, - "class" => "subTitle" - }).text = graph_subtitle.to_s - end - - if show_x_title - y = @graph_height + @border_top + x_title_font_size - if show_x_labels - y += x_label_font_size + 5 if stagger_x_labels - y += x_label_font_size + 5 - end - x = width / 2 - - @root.add_element("text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "xAxisTitle", - }).text = x_title.to_s - end - - if show_y_title - x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) - y = height / 2 - - text = @root.add_element("text", { - "x" => x.to_s, - "y" => y.to_s, - "class" => "yAxisTitle", - }) - text.text = y_title.to_s - if y_title_text_direction == :bt - text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" - else - text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" - end - end - end - - def keys - return @data.collect{ |d| d[:title] } - end - - # Draws the legend on the graph - def draw_legend - if key - group = @root.add_element( "g" ) - - key_count = 0 - for key_name in keys - y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) - group.add_element( "rect", { - "x" => 0.to_s, - "y" => y_offset.to_s, - "width" => KEY_BOX_SIZE.to_s, - "height" => KEY_BOX_SIZE.to_s, - "class" => "key#{key_count+1}" - }) - group.add_element( "text", { - "x" => (KEY_BOX_SIZE + 5).to_s, - "y" => (y_offset + KEY_BOX_SIZE).to_s, - "class" => "keyText" - }).text = key_name.to_s - key_count += 1 - end - - case key_position - when :right - x_offset = @graph_width + @border_left + 10 - y_offset = @border_top + 20 - when :bottom - x_offset = @border_left + 20 - y_offset = @border_top + @graph_height + 5 - if show_x_labels - max_x_label_height_px = (not rotate_x_labels) ? - x_label_font_size : - get_x_labels.max{|a,b| - a.to_s.length<=>b.to_s.length - }.to_s.length * x_label_font_size * 0.6 - x_label_font_size - y_offset += max_x_label_height_px - y_offset += max_x_label_height_px + 5 if stagger_x_labels - end - y_offset += x_title_font_size + 5 if show_x_title - end - group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" - end - end - - - private - - def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 ) - if lo < hi - p = partition(arrys,lo,hi) - sort_multiple(arrys, lo, p-1) - sort_multiple(arrys, p+1, hi) - end - arrys - end - - def partition( arrys, lo, hi ) - p = arrys[0][lo] - l = lo - z = lo+1 - while z <= hi - if arrys[0][z] < p - l += 1 - arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] } - end - z += 1 - end - arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] } - l - end - - def style - if no_css - styles = parse_css - @root.elements.each("//*[@class]") { |el| - cl = el.attributes["class"] - style = styles[cl] - style += el.attributes["style"] if el.attributes["style"] - el.attributes["style"] = style - } - end - end - - def parse_css - css = get_style - rv = {} - while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m - names_orig = names = $1 - css = $' - css =~ /([^}]+)\}/m - content = $1 - css = $' - - nms = [] - while names =~ /^\s*,?\s*\.(\w+)/ - nms << $1 - names = $' - end - - content = content.tr( "\n\t", " ") - for name in nms - current = rv[name] - current = current ? current+"; "+content : content - rv[name] = current.strip.squeeze(" ") - end - end - return rv - end - - - # Override and place code to add defs here - def add_defs defs - end - - - def start_svg - # Base document - @doc = Document.new - @doc << XMLDecl.new - @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + - %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) - if style_sheet && style_sheet != '' - @doc << Instruction.new( "xml-stylesheet", - %Q{href="#{style_sheet}" type="text/css"} ) - end - @root = @doc.add_element( "svg", { - "width" => width.to_s, - "height" => height.to_s, - "viewBox" => "0 0 #{width} #{height}", - "xmlns" => "http://www.w3.org/2000/svg", - "xmlns:xlink" => "http://www.w3.org/1999/xlink", - "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", - "a3:scriptImplementation" => "Adobe" - }) - @root << Comment.new( " "+"\\"*66 ) - @root << Comment.new( " Created with SVG::Graph " ) - @root << Comment.new( " SVG::Graph by Sean E. Russell " ) - @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+ - " Leo Lapworth & Stephan Morgan " ) - @root << Comment.new( " "+"/"*66 ) - - defs = @root.add_element( "defs" ) - add_defs defs - if not(style_sheet && style_sheet != '') and !no_css - @root << Comment.new(" include default stylesheet if none specified ") - style = defs.add_element( "style", {"type"=>"text/css"} ) - style << CData.new( get_style ) - end - - @root << Comment.new( "SVG Background" ) - @root.add_element( "rect", { - "width" => width.to_s, - "height" => height.to_s, - "x" => "0", - "y" => "0", - "class" => "svgBackground" - }) - end - - - def calculate_graph_dimensions - calculate_left_margin - calculate_right_margin - calculate_bottom_margin - calculate_top_margin - @graph_width = width - @border_left - @border_right - @graph_height = height - @border_top - @border_bottom - end - - def get_style - return < 'get', :path => "/projects/5234/memberships.xml" }, + { :controller => 'members', :action => 'index', :project_id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'show', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/5234/memberships" }, + { :controller => 'members', :action => 'create', :project_id => '5234' } + ) + assert_routing( + { :method => 'post', :path => "/projects/5234/memberships.xml" }, + { :controller => 'members', :action => 'create', :project_id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/memberships/5234" }, + { :controller => 'members', :action => 'update', :id => '5234' } + ) + assert_routing( + { :method => 'put', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'update', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/memberships/5234" }, + { :controller => 'members', :action => 'destroy', :id => '5234' } + ) + assert_routing( + { :method => 'delete', :path => "/memberships/5234.xml" }, + { :controller => 'members', :action => 'destroy', :id => '5234', :format => 'xml' } + ) + assert_routing( + { :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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08ba366b8c38c360363f9f7f4597d533502606b0.svn-base --- a/.svn/pristine/08/08ba366b8c38c360363f9f7f4597d533502606b0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,312 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 = WorkflowTransition.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 - WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) - WorkflowTransition.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 - WorkflowTransition.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, 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}) - 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, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) - 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}) - 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}) - 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}) - assert w.author - assert w.assignee - end - - def test_clear_workflow - assert WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0 - - post :edit, :role_id => 2, :tracker_id => 1 - assert_equal 0, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - end - - def test_get_permissions - get :permissions - - assert_response :success - assert_template 'permissions' - assert_not_nil assigns(:roles) - assert_not_nil assigns(:trackers) - end - - def test_get_permissions_with_role_and_tracker - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - get :permissions, :role_id => 1, :tracker_id => 2 - assert_response :success - assert_template 'permissions' - - assert_select 'input[name=role_id][value=1]' - assert_select 'input[name=tracker_id][value=2]' - - # Required field - assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]' - end - - # Read-only field - assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]' - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - - # Other field - assert_select 'select[name=?]', 'permissions[due_date][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - end - - def test_get_permissions_with_required_custom_field_should_not_show_required_option - cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) - - get :permissions, :role_id => 1, :tracker_id => 1 - assert_response :success - assert_template 'permissions' - - # Custom field that is always required - # The default option is "(Required)" - assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do - assert_select 'option[value=]' - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=required]', 0 - end - end - - def test_get_permissions_with_role_and_tracker_and_all_statuses - WorkflowTransition.delete_all - - get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0' - assert_response :success - assert_equal IssueStatus.sorted.all, assigns(:statuses) - end - - def test_post_permissions - WorkflowPermission.delete_all - - post :permissions, :role_id => 1, :tracker_id => 2, :permissions => { - 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''}, - 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''}, - 'due_date' => {'1' => '', '2' => '', '3' => ''}, - } - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 3, workflows.size - workflows.each do |workflow| - assert_equal 1, workflow.role_id - assert_equal 2, workflow.tracker_id - end - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'} - assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'} - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'} - end - - def test_post_permissions_should_clear_permissions - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - post :permissions, :role_id => 1, :tracker_id => 2 - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 2, workflows.size - assert wf1.reload - assert wf2.reload - end - - def test_get_copy - get :copy - assert_response :success - assert_template 'copy' - assert_select 'select[name=source_tracker_id]' do - assert_select 'option[value=1]', :text => 'Bug' - end - assert_select 'select[name=source_role_id]' do - assert_select 'option[value=2]', :text => 'Developer' - end - assert_select 'select[name=?]', 'target_tracker_ids[]' do - assert_select 'option[value=3]', :text => 'Support request' - end - assert_select 'select[name=?]', 'target_role_ids[]' do - assert_select 'option[value=1]', :text => 'Manager' - end - 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) - 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]} - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08d8c21c9156481395a983e5e0a4c9f73ad91ca9.svn-base --- a/.svn/pristine/08/08d8c21c9156481395a983e5e0a4c9f73ad91ca9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08f914636b6cd922b89f9ce8cb2268cea53be558.svn-base --- a/.svn/pristine/08/08f914636b6cd922b89f9ce8cb2268cea53be558.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 used to represent the relations of an issue -class IssueRelations < 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 - -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" - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/08/08fb7d1dbcb8591c64d100b93daff66b95400b98.svn-base --- a/.svn/pristine/08/08fb7d1dbcb8591c64d100b93daff66b95400b98.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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.find(:first, :conditions => {:builtin => 0}) && - !Tracker.find(:first) && - !IssueStatus.find(:first) && - !Enumeration.find(:first) - 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.find(:all).each { |t| - IssueStatus.find(:all).each { |os| - IssueStatus.find(: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| - [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.find(: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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09050a06606068974830186c1100d7dceca803ff.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09050a06606068974830186c1100d7dceca803ff.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/0930e7dcd668d1bc526e085d51f359ac0b2bfdf3.svn-base --- a/.svn/pristine/09/0930e7dcd668d1bc526e085d51f359ac0b2bfdf3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -api.user do - api.id @user.id - api.login @user.login if User.current.admin? - 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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09319bb04693b301a469674c2043f8c089fbdcbb.svn-base --- a/.svn/pristine/09/09319bb04693b301a469674c2043f8c089fbdcbb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4349 +0,0 @@ -#============================================================+ -# 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 -# - -@@version = "1.53.0.TC031" -@@fpdf_charwidths = {} - -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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09608744e321935ef94b4022df67d37162d9f786.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09608744e321935ef94b4022df67d37162d9f786.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,757 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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.deliver_issue_edit(journal) + + 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.deliver_issue_edit(journal) + + 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_issue_edit_should_generate_url_with_hostname_for_relations + journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now) + journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2) + Mailer.deliver_issue_edit(journal) + assert_not_nil last_email + assert_select_email do + assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2' + 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.deliver_issue_edit(journal) + + 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.deliver_issue_add(issue) + 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.deliver_issue_add(issue) + 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.deliver_issue_edit(journal) + 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.deliver_issue_edit(journal) + 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(2) + Mailer.deliver_issue_add(issue) + mail = last_email + assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", mail.references + end + + def test_issue_edit_message_id + journal = Journal.find(3) + journal.issue = Issue.find(2) + + Mailer.deliver_issue_edit(journal) + mail = last_email + assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.issue-2.20060719190421@example.net", 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_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", 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_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id + assert_include "redmine.message-1.20070512151532@example.net", 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.deliver_issue_add(issue) + 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.deliver_issue_add(issue) + 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.deliver_issue_add(issue) + 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.deliver_issue_add(issue) + assert !last_email.bcc.include?(user.mail) + end + + def test_issue_add_should_include_enabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + assert Mailer.deliver_issue_add(issue) + assert_mail_body_match '* Target version: 1.0', last_email + assert_select_email do + assert_select 'li', :text => 'Target version: 1.0' + end + end + + def test_issue_add_should_not_include_disabled_fields + Setting.default_language = 'en' + issue = Issue.find(2) + tracker = issue.tracker + tracker.core_fields -= ['fixed_version_id'] + tracker.save! + assert Mailer.deliver_issue_add(issue) + assert_mail_body_no_match 'Target version', last_email + assert_select_email do + assert_select 'li', :text => /Target version/, :count => 0 + end + 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.deliver_issue_add(issue) + end + end + + def test_issue_edit + journal = Journal.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.deliver_issue_edit(journal) + 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.deliver_issue_edit(journal) + assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort + + Role.find(2).remove_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + 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.deliver_issue_edit(journal) + assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + + Role.non_member.remove_permission! :view_private_notes + Mailer.deliver_issue_edit(journal) + assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + end + + def test_issue_edit_should_mark_private_notes + journal = Journal.find(2) + journal.private_notes = true + journal.save! + + with_settings :default_language => 'en' do + Mailer.deliver_issue_edit(journal) + end + assert_mail_body_match '(Private notes)', last_email + end + + def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue + issue = Issue.generate! + private_issue = Issue.generate!(:is_private => true) + IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates') + issue.reload + assert_equal 1, issue.journals.size + journal = issue.journals.first + ActionMailer::Base.deliveries.clear + + Mailer.deliver_issue_edit(journal) + last_email.bcc.each do |email| + user = User.find_by_mail(email) + assert private_issue.visible?(user), "Issue was not visible to #{user}" + end + 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 + with_settings :plain_text_mail => 0 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 + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "*Header content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_header + with_settings :emails_header => "", :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".header", false + end + end + end + + def test_layout_should_include_the_emails_footer + with_settings :emails_footer => "*Footer content*" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer" do + assert_select "strong", :text => "Footer content" + end + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_include "\n-- \n", mail.body.decoded + assert_include "*Footer content*", mail.body.decoded + end + end + end + + def test_layout_should_not_include_empty_emails_footer + with_settings :emails_footer => "" do + with_settings :plain_text_mail => 0 do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".footer", false + end + end + with_settings :plain_text_mail => 1 do + assert Mailer.test_email(User.find(1)).deliver + mail = last_email + assert_not_nil mail + assert_not_include "\n-- \n", mail.body.decoded + 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 + + def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + ActionMailer::Base.raise_delivery_errors = true + assert_raise Exception, "delivery error" do + mail.deliver + end + ensure + ActionMailer::Base.raise_delivery_errors = false + end + + def test_should_log_delivery_errors_when_raise_delivery_errors_is_false + mail = Mailer.test_email(User.find(1)) + mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error")) + + Rails.logger.expects(:error).with("Email delivery error: delivery error") + ActionMailer::Base.raise_delivery_errors = false + assert_nothing_raised do + mail.deliver + end + end + + def test_mail_should_return_a_mail_message + assert_kind_of ::Mail::Message, Mailer.test_email(User.find(1)) + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/099fe610435cd5107ad5a4a6139377c6eab6f41e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/099fe610435cd5107ad5a4a6139377c6eab6f41e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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/#{current_language.to_s.downcase}/wiki_syntax.html" + javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript url}'); 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-textile.min') + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09db2139c5f23fc2a9c87d7a056e38c3f53388a9.svn-base --- a/.svn/pristine/09/09db2139c5f23fc2a9c87d7a056e38c3f53388a9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1096 +0,0 @@ -# Estonian localization for Redmine -# Copyright (C) 2012 Kaitseministeerium -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -et: - # 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: [Pühapäev, Esmaspäev, Teisipäev, Kolmapäev, Neljapäev, Reede, Laupäev] - abbr_day_names: [P, E, T, K, N, R, L] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Jaanuar, Veebruar, Märts, Aprill, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] - abbr_month_names: [~, jaan, veebr, märts, apr, mai, juuni, juuli, aug, sept, okt, nov, dets] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%d.%m.%Y %H:%M" - time: "%H:%M" - short: "%d.%b %H:%M" - long: "%d. %B %Y %H:%M %z" - am: "enne lõunat" - pm: "peale lõunat" - - datetime: - distance_in_words: - half_a_minute: "pool minutit" - less_than_x_seconds: - one: "vähem kui sekund" - other: "vähem kui %{count} sekundit" - x_seconds: - one: "1 sekund" - other: "%{count} sekundit" - less_than_x_minutes: - one: "vähem kui minut" - other: "vähem kui %{count} minutit" - x_minutes: - one: "1 minut" - other: "%{count} minutit" - about_x_hours: - one: "umbes tund" - other: "umbes %{count} tundi" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 päev" - other: "%{count} päeva" - about_x_months: - one: "umbes kuu" - other: "umbes %{count} kuud" - x_months: - one: "1 kuu" - other: "%{count} kuud" - about_x_years: - one: "umbes aasta" - other: "umbes %{count} aastat" - over_x_years: - one: "rohkem kui aasta" - other: "rohkem kui %{count} aastat" - almost_x_years: - one: "peaaegu aasta" - other: "peaaegu %{count} aastat" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "bait" - other: "baiti" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "ja" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 viga ei võimaldanud selle %{model} salvestamist" - other: "%{count} viga ei võimaldanud selle %{model} salvestamist" - messages: - inclusion: "ei ole nimekirjas" - exclusion: "on reserveeritud" - invalid: "ei sobi" - confirmation: "ei lange kinnitusega kokku" - accepted: "peab olema aktsepteeritud" - empty: "ei või olla tühi" - blank: "ei või olla täitmata" - too_long: "on liiga pikk (lubatud on kuni %{count} märki)" - too_short: "on liiga lühike (vaja on vähemalt %{count} märki)" - wrong_length: "on vale pikkusega (peaks olema %{count} märki)" - taken: "on juba võetud" - not_a_number: "ei ole arv" - not_a_date: "ei ole korrektne kuupäev" - greater_than: "peab olema suurem kui %{count}" - greater_than_or_equal_to: "peab olema võrdne või suurem kui %{count}" - equal_to: "peab võrduma %{count}-ga" - less_than: "peab olema väiksem kui %{count}" - less_than_or_equal_to: "peab olema võrdne või väiksem kui %{count}" - odd: "peab olema paaritu arv" - even: "peab olema paarisarv" - greater_than_start_date: "peab olema suurem kui alguskuupäev" - not_same_project: "ei kuulu sama projekti juurde" - circular_dependency: "See suhe looks vastastikuse sõltuvuse" - cant_link_an_issue_with_a_descendant: "Teemat ei saa sidustada tema enda alamteemaga" - - actionview_instancetag_blank_option: "Palun vali" - - general_text_No: "Ei" - general_text_Yes: "Jah" - general_text_no: "ei" - general_text_yes: "jah" - general_lang_name: "Eesti" - general_csv_separator: "," - general_csv_decimal_separator: "." - general_csv_encoding: ISO-8859-13 - general_pdf_encoding: UTF-8 - general_first_day_of_week: "1" - - notice_account_updated: "Konto uuendamine õnnestus." - notice_account_invalid_creditentials: "Sobimatu kasutajanimi või parool" - notice_account_password_updated: "Parooli uuendamine õnnestus." - notice_account_wrong_password: "Vale parool" - notice_account_register_done: "Konto loomine õnnestus. Konto aktiveerimiseks vajuta vastaval lingil Sulle saadetud e-kirjas." - notice_account_unknown_email: "Tundmatu kasutaja." - notice_can_t_change_password: "See konto kasutab välist autentimisallikat. Siin ei saa selle konto parooli vahetada." - notice_account_lost_email_sent: "Sulle saadeti e-kiri parooli vahetamise juhistega." - notice_account_activated: "Su konto on aktiveeritud. Saad nüüd sisse logida." - notice_successful_create: "Loomine õnnestus." - notice_successful_update: "Uuendamine õnnestus." - notice_successful_delete: "Kustutamine õnnestus." - notice_successful_connection: "Ühenduse loomine õnnestus." - notice_file_not_found: "Sellist lehte ei leitud." - notice_locking_conflict: "Teine kasutaja uuendas vahepeal neid andmeid." - notice_not_authorized: "Sul ei ole sellele lehele ligipääsuks õigusi." - notice_not_authorized_archived_project: "See projekt on arhiveeritud." - notice_email_sent: "%{value}-le saadeti kiri" - notice_email_error: "Kirja saatmisel tekkis viga (%{value})" - notice_feeds_access_key_reseted: "Sinu RSS juurdepääsuvõti nulliti." - notice_api_access_key_reseted: "Sinu API juurdepääsuvõti nulliti." - notice_failed_to_save_issues: "%{count} teemat %{total}-st ei õnnestunud salvestada: %{ids}." - notice_failed_to_save_time_entries: "%{count} ajakulu kannet %{total}-st ei õnnestunud salvestada: %{ids}." - notice_failed_to_save_members: "Liiget/liikmeid ei õnnestunud salvestada: %{errors}." - notice_no_issue_selected: "Ühtegi teemat ei ole valitud! Palun vali teema(d), mida soovid muuta." - notice_account_pending: "Sinu konto on loodud ja ootab nüüd administraatori kinnitust." - notice_default_data_loaded: "Algseadistuste laadimine õnnestus." - notice_unable_delete_version: "Versiooni kustutamine ei õnnestunud." - notice_unable_delete_time_entry: "Ajakulu kande kustutamine ei õnnestunud." - notice_issue_done_ratios_updated: "Teema edenemise astmed on uuendatud." - notice_gantt_chart_truncated: "Diagrammi kärbiti kuna ületati kuvatavate objektide suurim hulk (%{max})" - notice_issue_successful_create: "Teema %{id} loodud." - notice_issue_update_conflict: "Teine kasutaja uuendas seda teemat Sinuga samaaegselt." - notice_account_deleted: "Sinu konto on lõplikult kustutatud." - - error_can_t_load_default_data: "Algseadistusi ei saanud laadida: %{value}" - error_scm_not_found: "Seda sissekannet hoidlast ei leitud." - error_scm_command_failed: "Hoidla poole pöördumisel tekkis viga: %{value}" - error_scm_annotate: "Sissekannet ei eksisteeri või ei saa annoteerida." - error_scm_annotate_big_text_file: "Sissekannet ei saa annoteerida, kuna see on liiga pikk." - error_issue_not_found_in_project: "Teemat ei leitud või see ei kuulu siia projekti" - error_no_tracker_in_project: "Selle projektiga ei ole seostatud ühtegi valdkonda. Palun vaata üle projekti seaded." - error_no_default_issue_status: 'Teema algolek on määramata. Palun vaata asetused üle ("Seadistused -> Olekud").' - error_can_not_delete_custom_field: "Omaloodud välja kustutamine ei õnnestunud" - error_can_not_delete_tracker: "See valdkond on mõnes teemas kasutusel ja seda ei saa kustutada." - error_can_not_remove_role: "See roll on mõnes projektis kasutusel ja seda ei saa kustutada." - error_can_not_reopen_issue_on_closed_version: "Suletud versiooni juurde kuulunud teemat ei saa taasavada" - error_can_not_archive_project: "Seda projekti ei saa arhiveerida" - error_issue_done_ratios_not_updated: "Teema edenemise astmed jäid uuendamata." - error_workflow_copy_source: "Palun vali algne valdkond või roll" - error_workflow_copy_target: "Palun vali sihtvaldkon(na)d või -roll(id)" - error_unable_delete_issue_status: "Oleku kustutamine ei õnnestunud" - error_unable_to_connect: "Ühenduse loomine ei õnnestunud (%{value})" - error_attachment_too_big: "Faili ei saa üles laadida, sest see on lubatust (%{max_size}) pikem" - warning_attachments_not_saved: "%{count} faili salvestamine ei õnnestunud." - - mail_subject_lost_password: "Sinu %{value} parool" - mail_body_lost_password: "Et vahetada oma parooli, vajuta järgmisele lingile:" - mail_subject_register: "Sinu %{value} konto aktiveerimine" - mail_body_register: "Et aktiveerida oma kontot, vajuta järgmisele lingile:" - mail_body_account_information_external: "Sisse logimiseks saad kasutada oma %{value} kontot." - mail_body_account_information: "Sinu konto teave" - mail_subject_account_activation_request: "%{value} konto aktiveerimise nõue" - mail_body_account_activation_request: "Registreerus uus kasutaja (%{value}). Konto avamine ootab Sinu kinnitust:" - mail_subject_reminder: "%{count} teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul" - mail_body_reminder: "%{count} Sulle määratud teema tähtaeg jõuab kätte järgmise %{days} päeva jooksul:" - mail_subject_wiki_content_added: "Lisati '%{id}' vikileht" - mail_body_wiki_content_added: "'%{id}' vikileht lisati %{author} poolt." - 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" - field_summary: "Kokkuvõte" - field_is_required: "Kohustuslik" - field_firstname: "Eesnimi" - field_lastname: "Perekonnanimi" - field_mail: "E-post" - field_filename: "Fail" - field_filesize: "Pikkus" - field_downloads: "Allalaadimist" - field_author: "Autor" - field_created_on: "Loodud" - field_updated_on: "Uuendatud" - field_field_format: "Tüüp" - field_is_for_all: "Kõigile projektidele" - field_possible_values: "Võimalikud väärtused" - field_regexp: "Regulaarne avaldis" - field_min_length: "Vähim pikkus" - field_max_length: "Suurim pikkus" - field_value: "Väärtus" - field_category: "Kategooria" - field_title: "Pealkiri" - field_project: "Projekt" - field_issue: "Teema" - field_status: "Olek" - field_notes: "Märkused" - field_is_closed: "Sulgeb teema" - field_is_default: "Algolek" - field_tracker: "Valdkond" - field_subject: "Teema" - field_due_date: "Tähtaeg" - field_assigned_to: "Tegeleja" - field_priority: "Prioriteet" - field_fixed_version: "Sihtversioon" - field_user: "Kasutaja" - field_principal: "Vastutav isik" - field_role: "Roll" - field_homepage: "Koduleht" - field_is_public: "Avalik" - field_parent: "Emaprojekt" - field_is_in_roadmap: "Teemad on teekaardil näha" - field_login: "Kasutajanimi" - field_mail_notification: "Teated e-kirjaga" - field_admin: "Admin" - field_last_login_on: "Viimane ühendus" - field_language: "Keel" - field_effective_date: "Tähtaeg" - field_password: "Parool" - field_new_password: "Uus parool" - field_password_confirmation: "Kinnitus" - field_version: "Versioon" - field_type: "Tüüp" - field_host: "Server" - field_port: "Port" - field_account: "Konto" - field_base_dn: "Baas DN" - field_attr_login: "Kasutajanime atribuut" - field_attr_firstname: "Eesnime atribuut" - field_attr_lastname: "Perekonnanime atribuut" - field_attr_mail: "E-posti atribuut" - field_onthefly: "Kasutaja automaatne loomine" - field_start_date: "Alguskuupäev" - field_done_ratio: "% tehtud" - field_auth_source: "Autentimise viis" - field_hide_mail: "Peida e-posti aadress" - field_comments: "Kommentaar" - field_url: "URL" - field_start_page: "Esileht" - field_subproject: "Alamprojekt" - field_hours: "tundi" - field_activity: "Tegevus" - field_spent_on: "Kuupäev" - field_identifier: "Tunnus" - field_is_filter: "Kasutatakse filtrina" - field_issue_to: "Seotud teema" - field_delay: "Viivitus" - field_assignable: "Saab määrata teemadega tegelema" - field_redirect_existing_links: "Suuna olemasolevad lingid ringi" - field_estimated_hours: "Eeldatav ajakulu" - field_column_names: "Veerud" - field_time_entries: "Ajakulu" - field_time_zone: "Ajatsoon" - field_searchable: "Otsitav" - field_default_value: "Vaikimisi" - field_comments_sorting: "Kommentaaride järjestus" - field_parent_title: "Pärineb lehest" - field_editable: "Muudetav" - field_watcher: "Jälgija" - field_identity_url: "OpenID URL" - field_content: "Sisu" - field_group_by: "Grupeeri tulemus" - field_sharing: "Teemade jagamine" - field_parent_issue: "Pärineb teemast" - field_member_of_group: "Tegeleja grupp" - field_assigned_to_role: "Tegeleja roll" - field_text: "Tekstiväli" - field_visible: "Nähtav" - field_warn_on_leaving_unsaved: "Hoiata salvestamata sisuga lehtedelt lahkumisel" - field_issues_visibility: "See roll näeb" - field_is_private: "Privaatne" - field_commit_logs_encoding: "Sissekannete kodeering" - field_scm_path_encoding: "Teeraja märkide kodeering" - field_path_to_repository: "Hoidla teerada" - field_root_directory: "Juurkataloog" - field_cvsroot: "CVSROOT" - field_cvs_module: "Moodul" - field_repository_is_default: "Peamine hoidla" - field_multiple: "Korraga mitu väärtust" - field_auth_source_ldap_filter: "LDAP filter" - - setting_app_title: "Veebilehe pealkiri" - setting_app_subtitle: "Veebilehe alampealkiri" - setting_welcome_text: "Tervitustekst" - setting_default_language: "Vaikimisi keel" - setting_login_required: "Autentimine kohustuslik" - setting_self_registration: "Omaloodud konto aktiveerimine" - setting_attachment_max_size: "Manuse suurim pikkus" - setting_issues_export_limit: "Teemade ekspordi limiit" - setting_mail_from: "Saatja e-posti aadress" - setting_bcc_recipients: "Saajaid ei näidata (lähevad BCC reale)" - setting_plain_text_mail: "E-kiri tavalise tekstina (ilma HTML-ta)" - setting_host_name: "Serveri nimi ja teerada" - setting_text_formatting: "Vormindamise abi" - setting_wiki_compression: "Viki ajaloo pakkimine" - setting_feeds_limit: "Atom voogude suurim objektide arv" - setting_default_projects_public: "Uued projektid on vaikimisi avalikud" - setting_autofetch_changesets: "Lae uuendused automaatselt" - setting_sys_api_enabled: "Hoidlate haldamine veebiteenuse kaudu" - setting_commit_ref_keywords: "Viitade võtmesõnad" - setting_commit_fix_keywords: "Paranduste võtmesõnad" - setting_autologin: "Automaatne sisselogimine" - setting_date_format: "Kuupäevaformaat" - setting_time_format: "Ajaformaat" - setting_cross_project_issue_relations: "Luba siduda eri projektide teemasid" - setting_issue_list_default_columns: "Teemade nimekirja vaikimisi veerud" - setting_repositories_encodings: "Manuste ja hoidlate kodeering" - setting_emails_header: "E-kirja päis" - setting_emails_footer: "E-kirja jalus" - setting_protocol: "Protokoll" - setting_per_page_options: "Objekte lehe kohta variandid" - setting_user_format: "Kasutaja nime esitamise vorm" - setting_activity_days_default: "Projektide ajalugu näidatakse" - setting_display_subprojects_issues: "Näita projektis vaikimisi ka alamprojektide teemasid" - setting_enabled_scm: "Kasutatavad lähtekoodi haldusvahendid" - setting_mail_handler_body_delimiters: "Kärbi e-kirja lõpp peale sellist rida" - setting_mail_handler_api_enabled: "E-kirjade vastuvõtt veebiteenuse kaudu" - setting_mail_handler_api_key: "Veebiteenuse API võti" - setting_sequential_project_identifiers: "Genereeri järjestikused projektitunnused" - setting_gravatar_enabled: "Kasuta Gravatari kasutajaikoone" - setting_gravatar_default: "Vaikimisi kasutatav ikoon" - setting_diff_max_lines_displayed: "Enim korraga näidatavaid erinevusi" - setting_file_max_size_displayed: "Kuvatava tekstifaili suurim pikkus" - setting_repository_log_display_limit: "Enim ajaloos näidatavaid sissekandeid" - setting_openid: "Luba OpenID-ga registreerimine ja sisselogimine" - setting_password_min_length: "Lühima lubatud parooli pikkus" - setting_new_project_user_role_id: "Projekti looja roll oma projektis" - setting_default_projects_modules: "Vaikimisi moodulid uutes projektides" - setting_issue_done_ratio: "Määra teema edenemise aste" - setting_issue_done_ratio_issue_field: "kasutades vastavat välja" - setting_issue_done_ratio_issue_status: "kasutades teema olekut" - setting_start_of_week: "Nädala alguspäev" - setting_rest_api_enabled: "Luba REST API kasutamine" - setting_cache_formatted_text: "Puhverda vormindatud teksti" - setting_default_notification_option: "Vaikimisi teavitatakse" - setting_commit_logtime_enabled: "Luba ajakulu sisestamine" - setting_commit_logtime_activity_id: "Tegevus kulunud ajal" - setting_gantt_items_limit: "Gantti diagrammi objektide suurim hulk" - setting_issue_group_assignment: "Luba teemade andmine gruppidele" - setting_default_issue_start_date_to_creation_date: "Uute teemade alguskuupäevaks teema loomise päev" - setting_commit_cross_project_ref: "Luba viiteid ja parandusi ka kõigi teiste projektide teemadele" - setting_unsubscribe: "Luba kasutajal oma konto kustutada" - - permission_add_project: "Projekte luua" - permission_add_subprojects: "Alamprojekte luua" - permission_edit_project: "Projekte muuta" - permission_select_project_modules: "Projektimooduleid valida" - permission_manage_members: "Liikmeid hallata" - permission_manage_project_activities: "Projekti tegevusi hallata" - permission_manage_versions: "Versioone hallata" - permission_manage_categories: "Kategooriaid hallata" - permission_view_issues: "Teemasid näha" - permission_add_issues: "Teemasid lisada" - permission_edit_issues: "Teemasid uuendada" - permission_manage_issue_relations: "Teemade seoseid hallata" - permission_set_issues_private: "Teemasid avalikeks või privaatseiks seada" - permission_set_own_issues_private: "Omi teemasid avalikeks või privaatseiks seada" - permission_add_issue_notes: "Märkusi lisada" - permission_edit_issue_notes: "Märkusi muuta" - permission_edit_own_issue_notes: "Omi märkusi muuta" - permission_move_issues: "Teemasid teise projekti tõsta" - permission_delete_issues: "Teemasid kustutada" - permission_manage_public_queries: "Avalikke päringuid hallata" - permission_save_queries: "Päringuid salvestada" - permission_view_gantt: "Gantti diagramme näha" - permission_view_calendar: "Kalendrit näha" - permission_view_issue_watchers: "Jälgijate nimekirja näha" - permission_add_issue_watchers: "Jälgijaid lisada" - permission_delete_issue_watchers: "Jälgijaid kustutada" - permission_log_time: "Ajakulu sisestada" - permission_view_time_entries: "Ajakulu näha" - permission_edit_time_entries: "Ajakulu kandeid muuta" - 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" - permission_manage_wiki: "Vikit hallata" - permission_rename_wiki_pages: "Vikilehti ümber nimetada" - permission_delete_wiki_pages: "Vikilehti kustutada" - permission_view_wiki_pages: "Vikit näha" - permission_view_wiki_edits: "Viki ajalugu näha" - permission_edit_wiki_pages: "Vikilehti muuta" - permission_delete_wiki_pages_attachments: "Manuseid kustutada" - permission_protect_wiki_pages: "Vikilehti kaitsta" - permission_manage_repository: "Hoidlaid hallata" - permission_browse_repository: "Hoidlaid sirvida" - permission_view_changesets: "Sissekandeid näha" - permission_commit_access: "Sissekandeid teha" - permission_manage_boards: "Foorumeid hallata" - permission_view_messages: "Postitusi näha" - permission_add_messages: "Postitusi lisada" - permission_edit_messages: "Postitusi muuta" - permission_edit_own_messages: "Omi postitusi muuta" - permission_delete_messages: "Postitusi kustutada" - permission_delete_own_messages: "Omi postitusi kustutada" - permission_export_wiki_pages: "Vikilehti eksportida" - permission_manage_subtasks: "Alamteemasid hallata" - permission_manage_related_issues: "Seotud teemasid hallata" - - project_module_issue_tracking: "Teemade jälgimine" - project_module_time_tracking: "Ajakulu arvestus" - project_module_news: "Uudised" - project_module_documents: "Dokumendid" - project_module_files: "Failid" - project_module_wiki: "Viki" - project_module_repository: "Hoidlad" - project_module_boards: "Foorumid" - project_module_calendar: "Kalender" - project_module_gantt: "Gantt" - - label_user: "Kasutaja" - label_user_plural: "Kasutajad" - label_user_new: "Uus kasutaja" - label_user_anonymous: "Anonüümne" - label_project: "Projekt" - label_project_new: "Uus projekt" - label_project_plural: "Projektid" - label_x_projects: - zero: "pole projekte" - one: "1 projekt" - other: "%{count} projekti" - label_project_all: "Kõik projektid" - label_project_latest: "Viimased projektid" - label_issue: "Teema" - label_issue_new: "Uus teema" - label_issue_plural: "Teemad" - label_issue_view_all: "Teemade nimekiri" - label_issues_by: "Teemad %{value} järgi" - label_issue_added: "Teema lisatud" - label_issue_updated: "Teema uuendatud" - label_issue_note_added: "Märkus lisatud" - label_issue_status_updated: "Olek uuendatud" - label_issue_priority_updated: "Prioriteet uuendatud" - label_document: "Dokument" - label_document_new: "Uus dokument" - label_document_plural: "Dokumendid" - label_document_added: "Dokument lisatud" - label_role: "Roll" - label_role_plural: "Rollid" - label_role_new: "Uus roll" - label_role_and_permissions: "Rollid ja õigused" - label_role_anonymous: "Anonüümne" - label_role_non_member: "Mitteliige" - label_member: "Liige" - label_member_new: "Uus liige" - label_member_plural: "Liikmed" - label_tracker: "Valdkond" - label_tracker_plural: "Valdkonnad" - label_tracker_new: "Uus valdkond" - label_workflow: "Töövood" - label_issue_status: "Olek" - label_issue_status_plural: "Olekud" - label_issue_status_new: "Uus olek" - label_issue_category: "Kategooria" - label_issue_category_plural: "Kategooriad" - label_issue_category_new: "Uus kategooria" - label_custom_field: "Omaloodud väli" - label_custom_field_plural: "Omaloodud väljad" - label_custom_field_new: "Uus väli" - label_enumerations: "Loetelud" - label_enumeration_new: "Uus väärtus" - label_information: "Teave" - label_information_plural: "Teave" - label_please_login: "Palun logi sisse" - label_register: "Registreeru" - label_login_with_open_id_option: "või logi sisse OpenID-ga" - label_password_lost: "Kui parool on ununud..." - label_home: "Kodu" - label_my_page: "Oma leht" - label_my_account: "Oma konto" - label_my_projects: "Oma projektid" - label_my_page_block: "Uus blokk" - label_administration: "Seadistused" - label_login: "Logi sisse" - label_logout: "Logi välja" - label_help: "Abi" - label_reported_issues: "Minu poolt lisatud teemad" - label_assigned_to_me_issues: "Minu teha olevad teemad" - label_last_login: "Viimane ühendus" - label_registered_on: "Registreeritud" - label_activity: "Ajalugu" - label_overall_activity: "Üldine tegevuste ajalugu" - label_user_activity: "%{value} tegevuste ajalugu" - label_new: "Uus" - label_logged_as: "Sisse logitud kui" - label_environment: "Keskkond" - label_authentication: "Autentimine" - label_auth_source: "Autentimisallikas" - label_auth_source_new: "Uus autentimisallikas" - label_auth_source_plural: "Autentimisallikad" - label_subproject_plural: "Alamprojektid" - label_subproject_new: "Uus alamprojekt" - label_and_its_subprojects: "%{value} ja selle alamprojektid" - label_min_max_length: "Min.-maks. pikkus" - label_list: "Nimekiri" - label_date: "Kuupäev" - label_integer: "Täisarv" - label_float: "Ujukomaarv" - label_boolean: "Tõeväärtus" - label_string: "Tekst" - 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" - label_attachment: "Fail" - label_attachment_new: "Uus fail" - label_attachment_delete: "Kustuta fail" - label_attachment_plural: "Failid" - label_file_added: "Fail lisatud" - label_report: "Aruanne" - label_report_plural: "Aruanded" - label_news: "Uudised" - label_news_new: "Lisa uudis" - label_news_plural: "Uudised" - label_news_latest: "Viimased uudised" - label_news_view_all: "Kõik uudised" - label_news_added: "Uudis lisatud" - label_news_comment_added: "Kommentaar uudisele lisatud" - label_settings: "Seaded" - label_overview: "Ülevaade" - label_version: "Versioon" - label_version_new: "Uus versioon" - label_version_plural: "Versioonid" - label_close_versions: "Sulge lõpetatud versioonid" - label_confirmation: "Kinnitus" - label_export_to: "Samuti saadaval kujul:" - label_read: "Loe..." - label_public_projects: "Avalikud projektid" - label_open_issues: "avatud" - label_open_issues_plural: "avatud" - label_closed_issues: "suletud" - label_closed_issues_plural: "suletud" - label_x_open_issues_abbr_on_total: - zero: "0 avatud / %{total}" - one: "1 avatud / %{total}" - other: "%{count} avatud / %{total}" - label_x_open_issues_abbr: - zero: "0 avatud" - one: "1 avatud" - other: "%{count} avatud" - label_x_closed_issues_abbr: - zero: "0 suletud" - one: "1 suletud" - other: "%{count} suletud" - label_x_issues: - zero: "0 teemat" - one: "1 teema" - other: "%{count} teemat" - label_total: "Kokku" - label_permissions: "Õigused" - label_current_status: "Praegune olek" - label_new_statuses_allowed: "Uued lubatud olekud" - label_all: "kõik" - label_none: "pole" - label_nobody: "eikeegi" - label_next: "Järgmine" - label_previous: "Eelmine" - label_used_by: "Kasutab" - label_details: "Üksikasjad" - label_add_note: "Lisa märkus" - label_per_page: "Lehe kohta" - label_calendar: "Kalender" - label_months_from: "kuu kaugusel" - label_gantt: "Gantt" - label_internal: "Sisemine" - label_last_changes: "viimased %{count} muudatust" - label_change_view_all: "Kõik muudatused" - label_personalize_page: "Kujunda leht ümber" - label_comment: "Kommentaar" - label_comment_plural: "Kommentaarid" - label_x_comments: - zero: "kommentaare pole" - one: "1 kommentaar" - other: "%{count} kommentaari" - label_comment_add: "Lisa kommentaar" - label_comment_added: "Kommentaar lisatud" - label_comment_delete: "Kustuta kommentaar" - label_query: "Omaloodud päring" - label_query_plural: "Omaloodud päringud" - label_query_new: "Uus päring" - label_my_queries: "Mu omaloodud päringud" - label_filter_add: "Lisa filter" - label_filter_plural: "Filtrid" - label_equals: "on" - label_not_equals: "ei ole" - label_in_less_than: "on väiksem kui" - label_in_more_than: "on suurem kui" - label_greater_or_equal: "suurem-võrdne" - label_less_or_equal: "väiksem-võrdne" - label_between: "vahemikus" - label_in: "sisaldub hulgas" - label_today: "täna" - label_all_time: "piirideta" - label_yesterday: "eile" - label_this_week: "sel nädalal" - label_last_week: "eelmisel nädalal" - label_last_n_days: "viimase %{count} päeva jooksul" - label_this_month: "sel kuul" - label_last_month: "eelmisel kuul" - label_this_year: "sel aastal" - label_date_range: "Kuupäevavahemik" - label_less_than_ago: "uuem kui" - label_more_than_ago: "vanem kui" - label_ago: "vanus" - label_contains: "sisaldab" - label_not_contains: "ei sisalda" - label_day_plural: "päeva" - label_repository: "Hoidla" - 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" - label_revision_plural: "Sissekanded" - label_revision_id: "Sissekande kood %{value}" - label_associated_revisions: "Seotud sissekanded" - label_added: "lisatud" - label_modified: "muudetud" - label_copied: "kopeeritud" - label_renamed: "ümber nimetatud" - label_deleted: "kustutatud" - label_latest_revision: "Viimane sissekanne" - label_latest_revision_plural: "Viimased sissekanded" - label_view_revisions: "Haru ajalugu" - label_view_all_revisions: "Kogu ajalugu" - label_max_size: "Suurim pikkus" - label_sort_highest: "Nihuta esimeseks" - label_sort_higher: "Nihuta üles" - label_sort_lower: "Nihuta alla" - label_sort_lowest: "Nihuta viimaseks" - label_roadmap: "Teekaart" - label_roadmap_due_in: "Tähtaeg %{value}" - label_roadmap_overdue: "%{value} hiljaks jäänud" - label_roadmap_no_issues: "Selles versioonis ei ole teemasid" - label_search: "Otsi" - label_result_plural: "Tulemused" - label_all_words: "Kõik sõnad" - label_wiki: "Viki" - label_wiki_edit: "Viki muutmine" - label_wiki_edit_plural: "Viki muutmised" - label_wiki_page: "Vikileht" - label_wiki_page_plural: "Vikilehed" - label_index_by_title: "Järjesta pealkirja järgi" - label_index_by_date: "Järjesta kuupäeva järgi" - label_current_version: "Praegune versioon" - label_preview: "Eelvaade" - label_feed_plural: "Vood" - label_changes_details: "Kõigi muudatuste üksikasjad" - label_issue_tracking: "Teemade jälgimine" - label_spent_time: "Kulutatud aeg" - label_overall_spent_time: "Kokku kulutatud aeg" - label_f_hour: "%{value} tund" - label_f_hour_plural: "%{value} tundi" - label_time_tracking: "Ajakulu arvestus" - label_change_plural: "Muudatused" - label_statistics: "Statistika" - label_commits_per_month: "Sissekandeid kuu kohta" - label_commits_per_author: "Sissekandeid autori kohta" - label_diff: "erinevused" - label_view_diff: "Vaata erinevusi" - label_diff_inline: "teksti sees" - label_diff_side_by_side: "kõrvuti" - label_options: "Valikud" - label_copy_workflow_from: "Kopeeri see töövoog" - label_permissions_report: "Õiguste aruanne" - label_watched_issues: "Jälgitud teemad" - label_related_issues: "Seotud teemad" - label_applied_status: "Kehtestatud olek" - label_loading: "Laadimas..." - label_relation_new: "Uus seos" - label_relation_delete: "Kustuta seos" - label_relates_to: "seostub" - label_duplicates: "duplitseerib" - label_duplicated_by: "duplitseerija" - label_blocks: "blokeerib" - label_blocked_by: "blokeerija" - label_precedes: "eelneb" - label_follows: "järgneb" - label_end_to_start: "lõpust alguseni" - label_end_to_end: "lõpust lõpuni" - label_start_to_start: "algusest alguseni" - label_start_to_end: "algusest lõpuni" - label_stay_logged_in: "Püsi sisselogituna" - label_disabled: "pole võimalik" - label_show_completed_versions: "Näita lõpetatud versioone" - label_me: "mina" - label_board: "Foorum" - label_board_new: "Uus foorum" - label_board_plural: "Foorumid" - label_board_locked: "Lukus" - label_board_sticky: "Püsiteema" - label_topic_plural: "Teemad" - label_message_plural: "Postitused" - label_message_last: "Viimane postitus" - label_message_new: "Uus postitus" - label_message_posted: "Postitus lisatud" - label_reply_plural: "Vastused" - label_send_information: "Saada teave konto kasutajale" - label_year: "Aasta" - label_month: "Kuu" - label_week: "Nädal" - label_date_from: "Alates" - label_date_to: "Kuni" - label_language_based: "Kasutaja keele põhjal" - label_sort_by: "Sorteeri %{value} järgi" - label_send_test_email: "Saada kontrollkiri" - label_feeds_access_key: "RSS juurdepääsuvõti" - label_missing_feeds_access_key: "RSS juurdepääsuvõti on puudu" - label_feeds_access_key_created_on: "RSS juurdepääsuvõti loodi %{value} tagasi" - label_module_plural: "Moodulid" - label_added_time_by: "Lisatud %{author} poolt %{age} tagasi" - label_updated_time_by: "Uuendatud %{author} poolt %{age} tagasi" - label_updated_time: "Uuendatud %{value} tagasi" - label_jump_to_a_project: "Ava projekt..." - label_file_plural: "Failid" - label_changeset_plural: "Muudatused" - label_default_columns: "Vaikimisi veerud" - label_no_change_option: "(Ei muutu)" - label_bulk_edit_selected_issues: "Muuda valitud teemasid korraga" - label_bulk_edit_selected_time_entries: "Muuda valitud ajakandeid korraga" - label_theme: "Visuaalne teema" - label_default: "Tavaline" - label_search_titles_only: "Ainult pealkirjadest" - label_user_mail_option_all: "Kõigist tegevustest kõigis mu projektides" - label_user_mail_option_selected: "Kõigist tegevustest ainult valitud projektides..." - label_user_mail_option_none: "Teavitusi ei saadeta" - label_user_mail_option_only_my_events: "Ainult mu jälgitavatest või minuga seotud tegevustest" - label_user_mail_option_only_assigned: "Ainult minu teha olevate asjade kohta" - label_user_mail_option_only_owner: "Ainult mu oma asjade kohta" - label_user_mail_no_self_notified: "Ära teavita mind mu enda tehtud muudatustest" - label_registration_activation_by_email: "e-kirjaga" - label_registration_manual_activation: "käsitsi" - label_registration_automatic_activation: "automaatselt" - label_display_per_page: "Lehe kohta: %{value}" - label_age: "Vanus" - label_change_properties: "Muuda omadusi" - label_general: "Üldine" - label_more: "Rohkem" - label_scm: "Lähtekoodi haldusvahend" - label_plugins: "Lisamoodulid" - label_ldap_authentication: "LDAP autentimine" - label_downloads_abbr: "A/L" - label_optional_description: "Teave" - label_add_another_file: "Lisa veel üks fail" - label_preferences: "Eelistused" - label_chronological_order: "kronoloogiline" - label_reverse_chronological_order: "tagurpidi kronoloogiline" - label_planning: "Planeerimine" - label_incoming_emails: "Sissetulevad e-kirjad" - label_generate_key: "Genereeri võti" - label_issue_watchers: "Jälgijad" - label_example: "Näide" - label_display: "Kujundus" - label_sort: "Sorteeri" - label_ascending: "Kasvavalt" - label_descending: "Kahanevalt" - label_date_from_to: "Alates %{start} kuni %{end}" - label_wiki_content_added: "Vikileht lisatud" - label_wiki_content_updated: "Vikileht uuendatud" - label_group: "Grupp" - label_group_plural: "Grupid" - label_group_new: "Uus grupp" - label_time_entry_plural: "Kulutatud aeg" - label_version_sharing_none: "ei toimu" - label_version_sharing_descendants: "alamprojektidega" - label_version_sharing_hierarchy: "projektihierarhiaga" - label_version_sharing_tree: "projektipuuga" - label_version_sharing_system: "kõigi projektidega" - label_update_issue_done_ratios: "Uuenda edenemise astmeid" - label_copy_source: "Allikas" - label_copy_target: "Sihtkoht" - label_copy_same_as_target: "Sama mis sihtkoht" - label_display_used_statuses_only: "Näita ainult selles valdkonnas kasutusel olekuid" - label_api_access_key: "API juurdepääsuvõti" - label_missing_api_access_key: "API juurdepääsuvõti on puudu" - label_api_access_key_created_on: "API juurdepääsuvõti loodi %{value} tagasi" - label_profile: "Profiil" - label_subtask_plural: "Alamteemad" - label_project_copy_notifications: "Saada projekti kopeerimise kohta teavituskiri" - label_principal_search: "Otsi kasutajat või gruppi:" - label_user_search: "Otsi kasutajat:" - label_additional_workflow_transitions_for_author: "Luba ka järgmisi üleminekuid kui kasutaja on teema looja" - label_additional_workflow_transitions_for_assignee: "Luba ka järgmisi üleminekuid kui kasutaja on teemaga tegeleja" - label_issues_visibility_all: "kõiki teemasid" - label_issues_visibility_public: "kõiki mitteprivaatseid teemasid" - label_issues_visibility_own: "enda poolt loodud või enda teha teemasid" - label_git_report_last_commit: "Viimase sissekande teave otse failinimekirja" - label_parent_revision: "Eellane" - label_child_revision: "Järglane" - label_export_options: "%{export_format} ekspordivalikud" - label_copy_attachments: "Kopeeri manused" - label_item_position: "%{position}/%{count}" - label_completed_versions: "Lõpetatud versioonid" - label_search_for_watchers: "Otsi lisamiseks jälgijaid" - - button_login: "Logi sisse" - button_submit: "Sisesta" - button_save: "Salvesta" - button_check_all: "Märgi kõik" - button_uncheck_all: "Nulli valik" - button_collapse_all: "Voldi kõik kokku" - button_expand_all: "Voldi kõik lahti" - button_delete: "Kustuta" - button_create: "Loo" - button_create_and_continue: "Loo ja jätka" - button_test: "Testi" - button_edit: "Muuda" - button_edit_associated_wikipage: "Muuda seotud vikilehte: %{page_title}" - button_add: "Lisa" - button_change: "Muuda" - button_apply: "Lae" - button_clear: "Puhasta" - button_lock: "Lukusta" - button_unlock: "Ava lukust" - button_download: "Lae alla" - button_list: "Listi" - button_view: "Vaata" - button_move: "Tõsta" - button_move_and_follow: "Tõsta ja järgne" - button_back: "Tagasi" - button_cancel: "Katkesta" - button_activate: "Aktiveeri" - button_sort: "Sorteeri" - button_log_time: "Ajakulu" - button_rollback: "Rulli tagasi sellesse versiooni" - button_watch: "Jälgi" - button_unwatch: "Ära jälgi" - button_reply: "Vasta" - button_archive: "Arhiveeri" - button_unarchive: "Arhiivist tagasi" - button_reset: "Nulli" - button_rename: "Nimeta ümber" - button_change_password: "Vaheta parool" - button_copy: "Kopeeri" - button_copy_and_follow: "Kopeeri ja järgne" - button_annotate: "Annoteeri" - button_update: "Muuda" - button_configure: "Konfigureeri" - button_quote: "Tsiteeri" - button_duplicate: "Duplitseeri" - button_show: "Näita" - button_edit_section: "Muuda seda sektsiooni" - button_export: "Ekspordi" - button_delete_my_account: "Kustuta oma konto" - - status_active: "aktiivne" - status_registered: "registreeritud" - status_locked: "lukus" - - version_status_open: "avatud" - version_status_locked: "lukus" - version_status_closed: "suletud" - - field_active: "Aktiivne" - - text_select_mail_notifications: "Tegevused, millest peaks e-kirjaga teavitama" - text_regexp_info: "nt. ^[A-Z0-9]+$" - text_min_max_length_info: "0 tähendab, et piiranguid ei ole" - text_project_destroy_confirmation: "Oled Sa kindel oma soovis see projekt täielikult kustutada?" - text_subprojects_destroy_warning: "Alamprojekt(id) - %{value} - kustutatakse samuti." - text_workflow_edit: "Töövoo muutmiseks vali roll ja valdkond" - text_are_you_sure: "Oled Sa kindel?" - text_journal_changed: "%{label} muudetud %{old} -> %{new}" - text_journal_changed_no_detail: "%{label} uuendatud" - text_journal_set_to: "%{label} uus väärtus on %{value}" - text_journal_deleted: "%{label} kustutatud (%{old})" - text_journal_added: "%{label} %{value} lisatud" - text_tip_issue_begin_day: "teema avamise päev" - text_tip_issue_end_day: "teema sulgemise päev" - text_tip_issue_begin_end_day: "teema avati ja sulgeti samal päeval" - text_project_identifier_info: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." - text_caracters_maximum: "%{count} märki kõige rohkem." - text_caracters_minimum: "Peab olema vähemalt %{count} märki pikk." - text_length_between: "Pikkus %{min} kuni %{max} märki." - text_tracker_no_workflow: "Selle valdkonna jaoks ei ole ühtegi töövoogu kirjeldatud" - text_unallowed_characters: "Lubamatud märgid" - text_comma_separated: "Lubatud erinevad väärtused (komaga eraldatult)." - text_line_separated: "Lubatud erinevad väärtused (igaüks eraldi real)." - text_issues_ref_in_commit_messages: "Teemadele ja parandustele viitamine sissekannete märkustes" - text_issue_added: "%{author} lisas uue teema %{id}." - text_issue_updated: "%{author} uuendas teemat %{id}." - text_wiki_destroy_confirmation: "Oled Sa kindel oma soovis kustutada see Viki koos kogu sisuga?" - text_issue_category_destroy_question: "Kustutatavat kategooriat kasutab %{count} teema(t). Mis Sa soovid nendega ette võtta?" - text_issue_category_destroy_assignments: "Jäta teemadel kategooria määramata" - text_issue_category_reassign_to: "Määra teemad teise kategooriasse" - text_user_mail_option: "Valimata projektidest saad teavitusi ainult jälgitavate või Sinuga seotud asjade kohta (nt. Sinu loodud või teha teemad)." - text_no_configuration_data: "Rollid, valdkonnad, olekud ja töövood ei ole veel seadistatud.\nVäga soovitav on laadida vaikeasetused. Peale laadimist saad neid ise muuta." - text_load_default_configuration: "Lae vaikeasetused" - text_status_changed_by_changeset: "Kehtestatakse muudatuses %{value}." - text_time_logged_by_changeset: "Kehtestatakse muudatuses %{value}." - text_issues_destroy_confirmation: "Oled Sa kindel oma soovis valitud teema(d) kustutada?" - text_issues_destroy_descendants_confirmation: "See kustutab samuti %{count} alamteemat." - text_time_entries_destroy_confirmation: "Oled Sa kindel oma soovis valitud ajakulu kanne/kanded kustutada?" - text_select_project_modules: "Projektis kasutatavad moodulid" - text_default_administrator_account_changed: "Algne administraatori konto on muudetud" - text_file_repository_writable: "Manuste kataloog on kirjutatav" - text_plugin_assets_writable: "Lisamoodulite abifailide kataloog on kirjutatav" - text_rmagick_available: "RMagick on kasutatav (mittekohustuslik)" - text_destroy_time_entries_question: "Kustutatavatele teemadele oli kirja pandud %{hours} tundi. Mis Sa soovid ette võtta?" - text_destroy_time_entries: "Kustuta need tunnid" - text_assign_time_entries_to_project: "Vii tunnid üle teise projekti" - text_reassign_time_entries: "Määra tunnid sellele teemale:" - text_user_wrote: "%{value} kirjutas:" - text_enumeration_destroy_question: "Selle väärtusega on seotud %{count} objekt(i)." - text_enumeration_category_reassign_to: "Seo nad teise väärtuse külge:" - text_email_delivery_not_configured: "E-kirjade saatmine ei ole seadistatud ja teavitusi ei saadeta.\nKonfigureeri oma SMTP server failis config/configuration.yml ja taaskäivita Redmine." - text_repository_usernames_mapping: "Seosta Redmine kasutaja hoidlasse sissekannete tegijaga.\nSama nime või e-postiga kasutajad seostatakse automaatselt." - text_diff_truncated: "... Osa erinevusi jäi välja, sest neid on näitamiseks liiga palju." - text_custom_field_possible_values_info: "Üks rida iga väärtuse jaoks" - text_wiki_page_destroy_question: "Sel lehel on %{descendants} järglasleht(e) ja järeltulija(t). Mis Sa soovid ette võtta?" - text_wiki_page_nullify_children: "Muuda järglaslehed uuteks juurlehtedeks" - text_wiki_page_destroy_children: "Kustuta järglaslehed ja kõik nende järglased" - text_wiki_page_reassign_children: "Määra järglaslehed teise lehe külge" - text_own_membership_delete_confirmation: "Sa võtad endalt ära osa või kõik õigused ega saa edaspidi seda projekti võib-olla enam muuta.\nOled Sa jätkamises kindel?" - text_zoom_in: "Vaata lähemalt" - text_zoom_out: "Vaata kaugemalt" - text_warn_on_leaving_unsaved: "Sel lehel on salvestamata teksti, mis läheb kaduma, kui siit lehelt lahkud." - text_scm_path_encoding_note: "Vaikimisi UTF-8" - text_git_repository_note: "Hoidla peab olema paljas (bare) ja kohalik (nt. /gitrepo, c:\\gitrepo)" - text_mercurial_repository_note: "Hoidla peab olema kohalik (nt. /hgrepo, c:\\hgrepo)" - text_scm_command: "Hoidla poole pöördumise käsk" - text_scm_command_version: "Versioon" - text_scm_config: "Hoidlate poole pöördumist saab konfigureerida failis config/configuration.yml. Peale selle muutmist taaskäivita Redmine." - text_scm_command_not_available: "Hoidla poole pöördumine ebaõnnestus. Palun kontrolli seadistusi." - text_issue_conflict_resolution_overwrite: "Kehtesta oma muudatused (kõik märkused jäävad, ent muu võidakse üle kirjutada)" - text_issue_conflict_resolution_add_notes: "Lisa oma märkused, aga loobu teistest muudatustest" - text_issue_conflict_resolution_cancel: "Loobu kõigist muudatustest ja lae %{link} uuesti" - text_account_destroy_confirmation: "Oled Sa kindel?\nSu konto kustutatakse jäädavalt ja seda pole võimalik taastada." - - default_role_manager: "Haldaja" - default_role_developer: "Arendaja" - default_role_reporter: "Edastaja" - default_tracker_bug: "Veaparandus" - default_tracker_feature: "Täiendus" - default_tracker_support: "Klienditugi" - default_issue_status_new: "Avatud" - default_issue_status_in_progress: "Töös" - default_issue_status_resolved: "Lahendatud" - default_issue_status_feedback: "Tagasiside" - default_issue_status_closed: "Suletud" - default_issue_status_rejected: "Tagasi lükatud" - default_doc_category_user: "Juhend lõppkasutajale" - default_doc_category_tech: "Tehniline dokumentatsioon" - default_priority_low: "Aega on" - default_priority_normal: "Tavaline" - default_priority_high: "Pakiline" - default_priority_urgent: "Täna vaja" - default_priority_immediate: "Kohe vaja" - default_activity_design: "Kavandamine" - default_activity_development: "Arendamine" - - enumeration_issue_priorities: "Teemade prioriteedid" - enumeration_doc_categories: "Dokumentide kategooriad" - enumeration_activities: "Tegevused (ajakulu)" - enumeration_system_activity: "Süsteemi aktiivsus" - description_filter: "Filter" - description_search: "Otsinguväli" - description_choose_project: "Projektid" - description_project_scope: "Otsingu ulatus" - description_notes: "Märkused" - description_message_content: "Postituse sisu" - description_query_sort_criteria_attribute: "Sorteerimise kriteerium" - description_query_sort_criteria_direction: "Sorteerimise suund" - description_user_mail_notification: "E-kirjaga teavitamise seaded" - description_available_columns: "Kasutatavad veerud" - description_selected_columns: "Valitud veerud" - description_all_columns: "Kõik veerud" - description_issue_category_reassign: "Vali uus kategooria" - description_wiki_subpages_reassign: "Vali lehele uus vanem" - description_date_range_list: "Vali vahemik nimekirjast" - description_date_range_interval: "Vali vahemik algus- ja lõpukuupäeva abil" - description_date_from: "Sisesta alguskuupäev" - description_date_to: "Sisesta lõpukuupäev" - 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: "Lubatud on ainult väikesed tähed (a-z), numbrid ja kriipsud.
    Peale salvestamist ei saa tunnust enam muuta." - 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: "kõik" - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: "alamprojektidega" - label_cross_project_tree: "projektipuuga" - label_cross_project_hierarchy: "projektihierarhiaga" - label_cross_project_system: "kõigi projektidega" - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09e163f7a5ba48f41a7d0433361b8d9b46760493.svn-base --- a/.svn/pristine/09/09e163f7a5ba48f41a7d0433361b8d9b46760493.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -<%= 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09ea8a2e3065e6d0cfba8be5ebe32c3e0767f60e.svn-base --- a/.svn/pristine/09/09ea8a2e3065e6d0cfba8be5ebe32c3e0767f60e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/09/09ee4c362357d89f421e143b7a604276903929a0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09ee4c362357d89f421e143b7a604276903929a0.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0a22e26c056016673b5cd0813b12a72fc4e8cff7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0a22e26c056016673b5cd0813b12a72fc4e8cff7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,145 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0a4b990585c7f952b85cc821c139a4f90df28825.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0a4b990585c7f952b85cc821c139a4f90df28825.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,113 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 TimelogCustomFieldsVisibilityTest < ActionController::TestCase + tests TimelogController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + TimeEntry.generate!(:issue => @issue) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :project_id => 1, :issue_id => @issue.id, :c => (['hours'] + @fields.map{|f| "issue.cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility_should_show_visible_custom_fields_only + Issue.delete_all + TimeEntry.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'})) + TimeEntry.generate!(:issue => Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'})) + + @request.session[:user_id] = user.id + get :index, :c => ["hours", "issue.cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :set_filter => '1', "issue.cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:entries).map{|i| i.issue.custom_field_value(@field2)}.sort + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0a53e228ad2ca76e975fdb76cc443721ba07788d.svn-base --- a/.svn/pristine/0a/0a53e228ad2ca76e975fdb76cc443721ba07788d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 => 30 - - before_destroy :remove_references_before_destroy - - scope :sorted, order("#{table_name}.lastname ASC") - - 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.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, *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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0a7193067757b43aa4a973fedb71a5a6507643df.svn-base --- a/.svn/pristine/0a/0a7193067757b43aa4a973fedb71a5a6507643df.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0ac45c1808f5ee15f2487069dcbe64385e65e79f.svn-base --- a/.svn/pristine/0a/0ac45c1808f5ee15f2487069dcbe64385e65e79f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - - CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze - # Fields that can be disabled - # Other (future) fields should be appended, not inserted! - CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio).freeze - CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze - - before_destroy :check_integrity - has_many :issues - has_many :workflow_rules, :dependent => :delete_all do - def copy(source_tracker) - WorkflowRule.copy(source_tracker, nil, proxy_association.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 - - attr_protected :field_bits - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - - scope :sorted, order("#{table_name}.position ASC") - scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - - def to_s; name end - - def <=>(tracker) - position <=> tracker.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 = WorkflowTransition. - connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'"). - flatten. - uniq - - @issue_statuses = IssueStatus.find_all_by_id(ids).sort - end - - def disabled_core_fields - i = -1 - @disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0} - end - - def core_fields - CORE_FIELDS - disabled_core_fields - end - - def core_fields=(fields) - raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array) - - bits = 0 - CORE_FIELDS.each_with_index do |field, i| - unless fields.include?(field) - bits |= 2 ** i - end - end - self.fields_bits = bits - @disabled_core_fields = nil - core_fields - end - - # Returns the fields that are disabled for all the given trackers - def self.disabled_core_fields(trackers) - if trackers.present? - trackers.uniq.map(&:disabled_core_fields).reduce(:&) - else - [] - end - end - - # Returns the fields that are enabled for one tracker at least - def self.core_fields(trackers) - if trackers.present? - trackers.uniq.map(&:core_fields).reduce(:|) - else - CORE_FIELDS.dup - end - end - -private - def check_integrity - raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any? - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0ad2fa6a29d7061cdbc80b5e79aaef4886193bb6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0ad2fa6a29d7061cdbc80b5e79aaef4886193bb6.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'zlib' + +class WikiContent < ActiveRecord::Base + self.locking_column = 'version' + belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + validates_presence_of :text + validates_length_of :comments, :maximum => 255, :allow_nil => true + + acts_as_versioned + + after_save :send_notification + + def visible?(user=User.current) + page.visible?(user) + end + + def project + page.project + end + + def attachments + page.nil? ? [] : page.attachments + end + + # Returns the mail adresses of users that should be notified + def recipients + notified = project.notified_users + notified.reject! {|user| !visible?(user)} + notified.collect(&:mail) + end + + # Return true if the content is the current page content + def current_version? + true + end + + class Version + belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id' + belongs_to :author, :class_name => '::User', :foreign_key => 'author_id' + attr_protected :data + + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, + :description => :comments, + :datetime => :updated_on, + :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', + :timestamp => "#{WikiContent.versioned_table_name}.updated_on", + :author_key => "#{WikiContent.versioned_table_name}.author_id", + :permission => :view_wiki_edits, + :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + + "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + + "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + + "#{WikiContent.versioned_table_name}.id", + :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + + "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + + "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} + + after_destroy :page_update_after_destroy + + def text=(plain) + case Setting.wiki_compression + when 'gzip' + begin + self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) + self.compression = 'gzip' + rescue + self.data = plain + self.compression = '' + end + else + self.data = plain + self.compression = '' + end + plain + end + + def text + @text ||= begin + str = case compression + when 'gzip' + Zlib::Inflate.inflate(data) + else + # uncompressed data + data + end + str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + str + end + end + + def project + page.project + end + + # Return true if the content is the current page content + def current_version? + page.content.version == self.version + end + + # Returns the previous version or nil + def previous + @previous ||= WikiContent::Version. + reorder('version DESC'). + includes(:author). + where("wiki_content_id = ? AND version < ?", wiki_content_id, version).first + end + + # Returns the next version or nil + def next + @next ||= WikiContent::Version. + reorder('version ASC'). + includes(:author). + where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first + end + + private + + # Updates page's content if the latest version is removed + # or destroys the page if it was the only version + def page_update_after_destroy + latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first + if latest && page.content.version != latest.version + raise ActiveRecord::Rollback unless page.content.revert_to!(latest) + elsif latest.nil? + raise ActiveRecord::Rollback unless page.destroy + end + end + end + + private + + def send_notification + # new_record? returns false in after_save callbacks + if id_changed? + if Setting.notified_events.include?('wiki_content_added') + Mailer.wiki_content_added(self).deliver + end + elsif text_changed? + if Setting.notified_events.include?('wiki_content_updated') + Mailer.wiki_content_updated(self).deliver + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0a/0aeabf349bacf0ce65e0c6107e082ed00e2f7277.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0aeabf349bacf0ce65e0c6107e082ed00e2f7277.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,79 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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] + validate :validate_user + + # Returns true if at least one object among objects is watched by user + def self.any_watched?(objects, user) + objects = objects.reject(&:new_record?) + if objects.any? + objects.group_by {|object| object.class.base_class}.each do |base_class, objects| + if Watcher.where(:watchable_type => base_class.name, :watchable_id => objects.map(&:id), :user_id => user.id).exists? + return true + end + end + end + false + end + + # 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.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user| + pruned += prune_single_user(user, options) + end + pruned + end + end + + protected + + def validate_user + 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 + where(:user_id => user.id).all.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0b1931e7310d3bdeabe0a2c171beeaecda67ff41.svn-base --- a/.svn/pristine/0b/0b1931e7310d3bdeabe0a2c171beeaecda67ff41.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0b26e69e5ae731de14e1272a803c5ca8472b3a28.svn-base --- a/.svn/pristine/0b/0b26e69e5ae731de14e1272a803c5ca8472b3a28.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -class AddRepositoriesPermissions < ActiveRecord::Migration - # model removed - class Permission < ActiveRecord::Base; end - - def self.up - Permission.create :controller => "repositories", :action => "show", :description => "button_view", :sort => 1450, :is_public => true - Permission.create :controller => "repositories", :action => "browse", :description => "label_browse", :sort => 1460, :is_public => true - Permission.create :controller => "repositories", :action => "entry", :description => "entry", :sort => 1462, :is_public => true - Permission.create :controller => "repositories", :action => "revisions", :description => "label_view_revisions", :sort => 1470, :is_public => true - Permission.create :controller => "repositories", :action => "revision", :description => "label_view_revisions", :sort => 1472, :is_public => true - Permission.create :controller => "repositories", :action => "diff", :description => "diff", :sort => 1480, :is_public => true - 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 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0b2cfb5e453d4c5b12b6d8b9ca9e37acb99e7715.svn-base --- a/.svn/pristine/0b/0b2cfb5e453d4c5b12b6d8b9ca9e37acb99e7715.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0b8a89ac0b31721906c0bc4fded1743272e7b058.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b8a89ac0b31721906c0bc4fded1743272e7b058.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,114 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, *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::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 + load_entries_changesets(entries) + 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.filechanges.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0b99b1cce55da066eba41dff8f2bfa290c20b955.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b99b1cce55da066eba41dff8f2bfa290c20b955.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,71 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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/setup' + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0bc8b19a332c3f8ffc74fbaa182a97b748ff7feb.svn-base --- a/.svn/pristine/0b/0bc8b19a332c3f8ffc74fbaa182a97b748ff7feb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0bec89d5c0dbedaa5bc5e8099c531dc64fe9abe2.svn-base --- a/.svn/pristine/0b/0bec89d5c0dbedaa5bc5e8099c531dc64fe9abe2.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0b/0bf8f19f5486edff5718369b36db7c358c6ec343.svn-base --- a/.svn/pristine/0b/0bf8f19f5486edff5718369b36db7c358c6ec343.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -<%= labelled_fields_for :issue, @issue do |f| %> - -
    -
    -<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %> -

    <%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true}, - :onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %>

    - -<% else %> -

    <%= h(@issue.status.name) %>

    -<% end %> - -<% if @issue.safe_attribute? 'priority_id' %> -

    <%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'assigned_to_id' %> -

    <%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %>

    -<% end %> - -<% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %> -

    <%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %> -<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), - new_project_issue_category_path(@issue.project), - :remote => true, - :method => 'get', - :title => l(:label_issue_category_new), - :tabindex => 200) if User.current.allowed_to?(:manage_categories, @issue.project) %>

    -<% end %> - -<% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %> -

    <%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %> -<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), - new_project_version_path(@issue.project), - :remote => true, - :method => 'get', - :title => l(:label_version_new), - :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %> -

    -<% end %> -
    - -
    -<% if @issue.safe_attribute? 'parent_issue_id' %> -

    <%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %>

    -<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path}')" %> -<% end %> - -<% if @issue.safe_attribute? 'start_date' %> -

    <%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('start_date') %><%= calendar_for('issue_start_date') if @issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'due_date' %> -

    <%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('due_date') %><%= calendar_for('issue_due_date') if @issue.leaf? %>

    -<% end %> - -<% if @issue.safe_attribute? 'estimated_hours' %> -

    <%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf?, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %>

    -<% end %> - -<% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %> -

    <%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %>

    -<% end %> -
    -
    - -<% if @issue.safe_attribute? 'custom_field_values' %> -<%= render :partial => 'issues/form_custom_fields' %> -<% end %> - -<% end %> - -<% include_calendar_headers_tags %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0c006adf29e0abdbaa9644cee502d6882d303828.svn-base --- a/.svn/pristine/0c/0c006adf29e0abdbaa9644cee502d6882d303828.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0c20b053aa02a18ebfa242903b54c5d2423345a1.svn-base --- a/.svn/pristine/0c/0c20b053aa02a18ebfa242903b54c5d2423345a1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingRolesTest < ActionController::IntegrationTest - def test_roles - assert_routing( - { :method => 'get', :path => "/roles" }, - { :controller => 'roles', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/roles.xml" }, - { :controller => 'roles', :action => 'index', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/roles/2.xml" }, - { :controller => 'roles', :action => 'show', :id => '2', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/roles/new" }, - { :controller => 'roles', :action => 'new' } - ) - assert_routing( - { :method => 'post', :path => "/roles" }, - { :controller => 'roles', :action => 'create' } - ) - assert_routing( - { :method => 'get', :path => "/roles/2/edit" }, - { :controller => 'roles', :action => 'edit', :id => '2' } - ) - assert_routing( - { :method => 'put', :path => "/roles/2" }, - { :controller => 'roles', :action => 'update', :id => '2' } - ) - assert_routing( - { :method => 'delete', :path => "/roles/2" }, - { :controller => 'roles', :action => 'destroy', :id => '2' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/roles/permissions" }, - { :controller => 'roles', :action => 'permissions' } - ) - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0c52d5a82094593c338acef265e5dd3106b35b1d.svn-base --- a/.svn/pristine/0c/0c52d5a82094593c338acef265e5dd3106b35b1d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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', { }, 'HTTP_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', { }, 'HTTP_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', { }, 'HTTP_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 - - def test_missing_template_should_respond_with_404 - get '/login.png' - assert_response 404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0c62ded19e437ec59035548bf5b8cb434884827a.svn-base --- a/.svn/pristine/0c/0c62ded19e437ec59035548bf5b8cb434884827a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1395 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - include Redmine::Utils::DateCalculation - - 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 :visible_journals, - :class_name => 'Journal', - :as => :journalized, - :conditions => Proc.new { - ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false] - }, - :readonly => true - - 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, :visible_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 - delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true - - 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, :validate_required_fields - - scope :visible, - lambda {|*args| { :include => :project, - :conditions => 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} - } - - 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}"] - - before_create :default_assign - before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change - 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 - after_save :after_create_from_copy - 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| - if user.logged? - 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 - else - "(#{table_name}.is_private = #{connection.quoted_false})" - 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| - if user.logged? - 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 - else - !self.is_private? - end - end - end - - def initialize(attributes=nil, *args) - super - if new_record? - # set default values for new records only - self.status ||= IssueStatus.default - self.priority ||= IssuePriority.default - self.watcher_user_ids = [] - end - end - - # 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 - # (because an issue may already be deleted if its parent was deleted - # first). - # The issue is reloaded by the nested_set before being deleted so - # the lock_version condition should not be an issue but we handle it. - def destroy - super - rescue ActiveRecord::RecordNotFound - # Stale or already deleted - begin - reload - rescue ActiveRecord::RecordNotFound - # The issue was actually already deleted - @destroyed = true - return freeze - end - # The issue was stale, retry to destroy - super - end - - def reload(*args) - @workflow_rule_by_attribute = nil - @assignable_versions = nil - super - 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 - - # Copies attributes from another issue, arg can be an id or an Issue - def copy_from(arg, options={}) - 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.author = User.current - unless options[:attachments] == false - self.attachments = issue.attachments.map do |attachement| - attachement.copy(:container => self) - end - end - @copied_from = issue - @copy_options = options - self - end - - # Returns an unsaved copy of the issue - def copy(attributes=nil, copy_options={}) - copy = self.class.new.copy_from(self, copy_options) - copy.attributes = attributes if attributes - copy - end - - # Returns true if the issue is a copy - def copy? - @copied_from.present? - 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(new_project, new_tracker=nil, options={}) - ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead." - - if options[:copy] - issue = self.copy - else - issue = self - end - - issue.init_journal(User.current, options[:notes]) - - # Preserve previous behaviour - # #move_to_project doesn't change tracker automatically - issue.send :project=, new_project, true - if new_tracker - issue.tracker = new_tracker - end - # Allow bulk setting of attributes on the issue - if options[:attributes] - issue.attributes = options[:attributes] - end - - issue.save ? issue : false - end - - def status_id=(sid) - self.status = nil - result = write_attribute(:status_id, sid) - @workflow_rule_by_attribute = nil - result - end - - def priority_id=(pid) - self.priority = nil - write_attribute(:priority_id, pid) - end - - def category_id=(cid) - self.category = nil - write_attribute(:category_id, cid) - end - - def fixed_version_id=(vid) - self.fixed_version = nil - write_attribute(:fixed_version_id, vid) - end - - def tracker_id=(tid) - self.tracker = nil - result = write_attribute(:tracker_id, tid) - @custom_field_values = nil - @workflow_rule_by_attribute = nil - result - end - - def project_id=(project_id) - if project_id.to_s != self.project_id.to_s - self.project = (project_id.present? ? Project.find_by_id(project_id) : nil) - end - end - - def project=(project, keep_tracker=false) - project_was = self.project - write_attribute(:project_id, project ? project.id : nil) - association_instance_set('project', project) - if project_was && project && project_was != project - @assignable_versions = nil - - unless keep_tracker || project.trackers.include?(tracker) - self.tracker = project.trackers.first - end - # Reassign to the category with same name if any - if category - self.category = project.issue_categories.find_by_name(category.name) - end - # Keep the fixed_version if it's still valid in the new_project - if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version) - self.fixed_version = nil - end - # Clear the parent task if it's no longer valid - unless valid_parent_project? - self.parent_issue_id = nil - end - @custom_field_values = nil - end - 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 assign_attributes so that project and tracker get assigned first - def assign_attributes_with_project_and_tracker_first(new_attributes, *args) - return if new_attributes.nil? - attrs = new_attributes.dup - attrs.stringify_keys! - - %w(project project_id tracker tracker_id).each do |attr| - if attrs.has_key?(attr) - send "#{attr}=", attrs.delete(attr) - end - end - send :assign_attributes_without_project_and_tracker_first, attrs, *args - end - # Do not redefine alias chain on reload (see #4838) - alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first) - - def estimated_hours=(h) - write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) - end - - safe_attributes 'project_id', - :if => lambda {|issue, user| - if issue.new_record? - issue.copy? - elsif user.allowed_to?(:move_issues, issue.project) - projects = Issue.allowed_target_projects_on_move(user) - projects.include?(issue.project) && projects.size > 1 - end - } - - safe_attributes 'tracker_id', - 'status_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', - 'notes', - :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', - 'lock_version', - 'notes', - :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } - - safe_attributes 'notes', - :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)} - - safe_attributes 'private_notes', - :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)} - - safe_attributes 'watcher_user_ids', - :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)} - - 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)) - } - - safe_attributes 'parent_issue_id', - :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) && - user.allowed_to?(:manage_subtasks, issue.project)} - - def safe_attribute_names(user=nil) - names = super - names -= disabled_core_fields - names -= read_only_attribute_names(user) - names - end - - # 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 - def safe_attributes=(attrs, user=User.current) - return unless attrs.is_a?(Hash) - - attrs = attrs.dup - - # Project and Tracker must be set before since new_statuses_allowed_to depends on it. - if (p = attrs.delete('project_id')) && safe_attribute?('project_id') - if allowed_target_projects(user).collect(&:id).include?(p.to_i) - self.project_id = p - end - end - - if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id') - self.tracker_id = t - end - - if (s = attrs.delete('status_id')) && safe_attribute?('status_id') - if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i) - self.status_id = s - end - end - - attrs = delete_unsafe_attributes(attrs, user) - return if attrs.empty? - - unless leaf? - attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)} - end - - if attrs['parent_issue_id'].present? - s = attrs['parent_issue_id'].to_s - unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1])) - @invalid_parent_issue_id = attrs.delete('parent_issue_id') - end - end - - if attrs['custom_field_values'].present? - attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s} - end - - if attrs['custom_fields'].present? - attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s} - end - - # mass-assignment security bypass - assign_attributes attrs, :without_protection => true - end - - def disabled_core_fields - tracker ? tracker.disabled_core_fields : [] - end - - # Returns the custom_field_values that can be edited by the given user - def editable_custom_field_values(user=nil) - custom_field_values.reject do |value| - read_only_attribute_names(user).include?(value.custom_field_id.to_s) - end - end - - # Returns the names of attributes that are read-only for user or the current user - # For users with multiple roles, the read-only fields are the intersection of - # read-only fields of each role - # The result is an array of strings where sustom fields are represented with their ids - # - # Examples: - # issue.read_only_attribute_names # => ['due_date', '2'] - # issue.read_only_attribute_names(user) # => [] - def read_only_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys - end - - # Returns the names of required attributes for user or the current user - # For users with multiple roles, the required fields are the intersection of - # required fields of each role - # The result is an array of strings where sustom fields are represented with their ids - # - # Examples: - # issue.required_attribute_names # => ['due_date', '2'] - # issue.required_attribute_names(user) # => [] - def required_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys - end - - # Returns true if the attribute is required for user - def required_attribute?(name, user=nil) - required_attribute_names(user).include?(name.to_s) - end - - # Returns a hash of the workflow rule by attribute for the given user - # - # Examples: - # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'} - def workflow_rule_by_attribute(user=nil) - return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil? - - user_real = user || User.current - roles = user_real.admin ? Role.all : user_real.roles_for_project(project) - return {} if roles.empty? - - result = {} - workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all - if workflow_permissions.any? - workflow_rules = workflow_permissions.inject({}) do |h, wp| - h[wp.field_name] ||= [] - h[wp.field_name] << wp.rule - h - end - workflow_rules.each do |attr, rules| - next if rules.size < roles.size - uniq_rules = rules.uniq - if uniq_rules.size == 1 - result[attr] = uniq_rules.first - else - result[attr] = 'required' - end - end - end - @workflow_rule_by_attribute = result if user.nil? - result - end - private :workflow_rule_by_attribute - - 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 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 - - 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 @invalid_parent_issue_id.present? - errors.add :parent_issue_id, :invalid - elsif @parent_issue - if !valid_parent_project?(@parent_issue) - errors.add :parent_issue_id, :invalid - 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, :invalid - end - end - end - end - - # Validates the issue against additional workflow requirements - def validate_required_fields - user = new_record? ? author : current_journal.try(:user) - - required_attribute_names(user).each do |attribute| - if attribute =~ /^\d+$/ - attribute = attribute.to_i - v = custom_field_values.detect {|v| v.custom_field_id == attribute } - if v && v.value.blank? - errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank') - end - else - if respond_to?(attribute) && send(attribute).blank? - errors.add attribute, :blank - 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) - if new_record? - @current_journal.notify = false - else - @attributes_before_change = attributes.dup - @custom_values_before_change = {} - self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } - end - @current_journal - end - - # Returns the id of the last journal or nil - def last_journal_id - if new_record? - nil - else - journals.maximum(:id) - end - end - - # Returns a scope for journals that have an id greater than journal_id - def journals_after(journal_id) - scope = journals.reorder("#{Journal.table_name}.id ASC") - if journal_id.present? - scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i) - end - scope - 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 - return @assignable_versions if @assignable_versions - - versions = project.shared_versions.open.all - if fixed_version - if fixed_version_id_changed? - # nothing to do - elsif project_id_changed? - if project.shared_versions.include?(fixed_version) - versions << fixed_version - end - else - versions << fixed_version - end - end - @assignable_versions = versions.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 statuses that user is able to apply - def new_statuses_allowed_to(user=User.current, include_default=false) - if new_record? && @copied_from - [IssueStatus.default, @copied_from.status].compact.uniq.sort - else - initial_status = nil - if new_record? - initial_status = IssueStatus.default - elsif status_id_was - initial_status = IssueStatus.find_by_id(status_id_was) - end - initial_status ||= status - - statuses = initial_status.find_new_statuses_allowed_to( - user.admin ? Role.all : user.roles_for_project(project), - tracker, - author == user, - assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id - ) - statuses << initial_status unless statuses.empty? - statuses << IssueStatus.default if include_default - statuses = statuses.compact.uniq.sort - blocked? ? statuses.reject {|s| s.is_closed?} : statuses - end - end - - def assigned_to_was - if assigned_to_id_changed? && assigned_to_id_was.present? - @assigned_to_was ||= User.find_by_id(assigned_to_id_was) - end - end - - # Returns the users that should be notified - def notified_users - notified = [] - # Author and assignee are always notified unless they have been - # locked or don't want to be notified - notified << author if author - if assigned_to - notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to]) - end - if assigned_to_was - notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was]) - end - notified = notified.select {|u| u.active? && u.notify_about?(self)} - - notified += project.notified_users - notified.uniq! - # Remove users that can not view the issue - notified.reject! {|user| !visible?(user)} - notified - end - - # Returns the email addresses that should be notified - def recipients - notified_users.collect(&:mail) - end - - # Returns the number of hours spent on this issue - def spent_hours - @spent_hours ||= time_entries.sum(:hours) || 0 - end - - # Returns the total number of hours spent on this issue and its descendants - # - # Example: - # spent_hours => 0.0 - # spent_hours => 50.2 - def total_spent_hours - @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", - :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0 - end - - def relations - @relations ||= IssueRelations.new(self, (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 - - # Preloads visible spent time for a collection of issues - def self.load_visible_spent_hours(issues, user=User.current) - if issues.any? - hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id) - issues.each do |issue| - issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0) - end - end - end - - # Preloads visible relations for a collection of issues - def self.load_visible_relations(issues, user=User.current) - if issues.any? - issue_ids = issues.map(&:id) - # Relations with issue_from in given issues and visible issue_to - relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all - # Relations with issue_to in given issues and visible issue_from - relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all - - issues.each do |issue| - relations = - 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) - 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 - - # Returns the duration in working days - def working_duration - (start_date && due_date) ? working_days(start_date, due_date) : 0 - end - - def soonest_start(reload=false) - @soonest_start = nil if reload - @soonest_start ||= ( - relations_to(reload).collect{|relation| relation.successor_soonest_start} + - ancestors.collect(&:soonest_start) - ).compact.max - end - - # Sets start_date on the given date or the next working day - # and changes due_date to keep the same working duration. - def reschedule_on(date) - wd = working_duration - date = next_working_date(date) - self.start_date = date - self.due_date = add_working_days(date, wd) - end - - # Reschedules the issue on the given date or the next working day and saves the record. - # If the issue is a parent task, this is done by rescheduling its subtasks. - def reschedule_on!(date) - return if date.nil? - if leaf? - if start_date.nil? || start_date != date - if start_date && start_date > date - # Issue can not be moved earlier than its soonest start date - date = [soonest_start(true), date].compact.max - end - reschedule_on(date) - begin - save - rescue ActiveRecord::StaleObjectError - reload - reschedule_on(date) - save - end - end - else - leaves.each do |leaf| - if leaf.start_date - # Only move subtask if it starts at the same date as the parent - # or if it starts before the given date - if start_date == leaf.start_date || date > leaf.start_date - leaf.reschedule_on!(date) - end - else - leaf.reschedule_on!(date) - end - 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_id} #{priority.try(:css_classes)}" - 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 and a time_entry from the parameters - 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 - - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - 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 - 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) - s = arg.to_s.strip.presence - if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1])) - @parent_issue.id - else - @parent_issue = nil - @invalid_parent_issue_id = arg - end - end - - def parent_issue_id - if @invalid_parent_issue_id - @invalid_parent_issue_id - elsif instance_variable_defined? :@parent_issue - @parent_issue.nil? ? nil : @parent_issue.id - else - parent_id - end - end - - # 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 - - case Setting.cross_project_subtasks - when 'system' - true - when 'tree' - issue.project.root == project.root - when 'hierarchy' - issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project) - when 'descendants' - issue.project.is_or_is_ancestor_of?(project) - else - false - 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 user can assign the issue to - def allowed_target_projects(user=User.current) - if new_record? - Project.all(:conditions => Project.allowed_to_condition(user, :add_issues)) - else - self.class.allowed_target_projects_on_move(user) - end - end - - # Returns an array of projects that user can move issues to - def self.allowed_target_projects_on_move(user=User.current) - Project.all(:conditions => Project.allowed_to_condition(user, :move_issues)) - end - - private - - def after_project_change - # Update project_id on related time entries - TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id}) - - # Delete issue relations - unless Setting.cross_project_issue_relations? - relations_from.clear - relations_to.clear - end - - # Move subtasks that were in the same project - children.each do |child| - next unless child.project_id == project_id_was - # Change project and keep project - child.send :project=, project, true - unless child.save - raise ActiveRecord::Rollback - end - end - end - - # Callback for after the creation of an issue by copy - # * adds a "copied to" relation with the copied issue - # * copies subtasks from the copied issue - def after_create_from_copy - return unless copy? && !@after_create_from_copy_handled - - if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false - relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO) - unless relation.save - logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger - end - end - - unless @copied_from.leaf? || @copy_options[:subtasks] == false - @copied_from.children.each do |child| - 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.author = author - copy.project = project - copy.parent_issue_id = id - # Children subtasks are copied recursively - 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 - end - end - end - @after_create_from_copy_handled = true - end - - 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", :joins => :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)", :joins => :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(:validate => 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.scoped(:conditions => conditions).all( - :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'", - :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 file attachment - 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) - if @current_journal && !obj.new_record? - @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename) - @current_journal.save - end - 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 - - # Make sure updated_on is updated when adding a note - def force_updated_on_change - if @current_journal - self.updated_on = current_time_from_proper_timezone - end - end - - # Saves the changes in a Journal - # Called after_save - def create_journal - 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| - before = @attributes_before_change[c] - after = send(c) - next if before == after || (before.blank? && after.blank?) - @current_journal.details << JournalDetail.new(:property => 'attr', - :prop_key => c, - :old_value => before, - :value => after) - } - end - if @custom_values_before_change - # custom fields changes - custom_field_values.each {|c| - before = @custom_values_before_change[c.custom_field_id] - after = c.value - next if before == after || (before.blank? && after.blank?) - - if before.is_a?(Array) || after.is_a?(Array) - before = [before] unless before.is_a?(Array) - after = [after] unless after.is_a?(Array) - - # values removed - (before - after).reject(&:blank?).each do |value| - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => value, - :value => nil) - end - # values added - (after - before).reject(&:blank?).each do |value| - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => nil, - :value => value) - end - else - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => before, - :value => after) - end - } - end - @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0c683f18d0752b5b1c900f3d3bf34e0652a0c13f.svn-base --- a/.svn/pristine/0c/0c683f18d0752b5b1c900f3d3bf34e0652a0c13f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,290 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0ca4bc46a7e22e354ba846105adab84836d7c7d6.svn-base --- a/.svn/pristine/0c/0ca4bc46a7e22e354ba846105adab84836d7c7d6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,780 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 =~ (/(\w+)(\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 - - old_notified_events = Setting.notified_events - old_password_min_length = Setting.password_min_length - begin - # Turn off email notifications temporarily - Setting.notified_events = [] - Setting.password_min_length = 4 - # Run the migration - TracMigrate.migrate - ensure - # Restore previous settings - Setting.notified_events = old_notified_events - Setting.password_min_length = old_password_min_length - end - end -end - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0cb1b271165f09325935c33b90607362679e5b26.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0cb1b271165f09325935c33b90607362679e5b26.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,23 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0cb33e746ce4443d0cb3bfc0a14c8cc1559914ac.svn-base --- a/.svn/pristine/0c/0cb33e746ce4443d0cb3bfc0a14c8cc1559914ac.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -<%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %> - ----------------------------------------- -<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0cc35d9ebd9809126c44031aad1c4acc705d8aec.svn-base --- a/.svn/pristine/0c/0cc35d9ebd9809126c44031aad1c4acc705d8aec.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0c/0ccef3aea9c789ba7ff6975c6015bb677b5a97a9.svn-base --- a/.svn/pristine/0c/0ccef3aea9c789ba7ff6975c6015bb677b5a97a9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0d/0d0d229a445c7f425386e30fa2d56de79c1a0ed1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d0d229a445c7f425386e30fa2d56de79c1a0ed1.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,48 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/0d/0dbc51ba6d0f40060a5f81b2175600adf04e0452.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0dbc51ba6d0f40060a5f81b2175600adf04e0452.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,42 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::TokenAuthenticationTest < 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. + should_allow_key_based_auth(:get, "/news.xml") + should_allow_key_based_auth(:get, "/news.json") +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0e/0e54508d3830a714379a5549a38e89ff26176202.svn-base --- a/.svn/pristine/0e/0e54508d3830a714379a5549a38e89ff26176202.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -

    <%= @author.nil? ? l(:label_activity) : l(:label_user_activity, link_to_user(@author)).html_safe %>

    -

    <%= l(:label_date_from_to, :start => format_date(@date_to - @days), :end => format_date(@date_to-1)) %>

    - -
    -<% @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| -%> -
    - <%= 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_user(e.event_author) if e.respond_to?(:event_author) %>
    -<% end -%> -
    -<% end -%> -
    - -<%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %> - -
    -<%= link_to_content_update("\xc2\xab " + l(:label_previous), - params.merge(:from => @date_to - @days - 1), - :title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))) %> -
    -
    -<%= link_to_content_update(l(:label_next) + " \xc2\xbb", - params.merge(:from => @date_to + @days - 1), - :title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))) unless @date_to >= Date.today %> -
    -  -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => params.merge(:from => nil, :key => User.current.rss_key) %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, params.merge(:format => 'atom', :from => nil, :key => User.current.rss_key)) %> -<% end %> - -<% content_for :sidebar do %> -<%= form_tag({}, :method => :get) do %> -

    <%= 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? %> - <%= hidden_field_tag 'with_subprojects', 0 %> -

    -<% end %> -<%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %> -

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

    -<% end %> -<% end %> - -<% html_title(l(:label_activity), @author) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0e/0e59eac074a412c3150c3135d0a1eec08baa4fba.svn-base --- a/.svn/pristine/0e/0e59eac074a412c3150c3135d0a1eec08baa4fba.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,265 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 'issues_controller' - -class IssuesControllerTransactionTest < 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 - - self.use_transactional_fixtures = false - - def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_update_stale_issue_should_not_update_the_issue - issue = Issue.find(2) - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - assert_no_difference 'TimeEntry.count' do - put :update, - :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => 'My notes', - :lock_version => (issue.lock_version - 1) - }, - :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id } - end - end - - assert_response :success - assert_template 'edit' - - assert_select 'div.conflict' - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'overwrite' - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'add_notes' - assert_select 'label' do - assert_select 'input[name=?][value=?]', 'conflict_resolution', 'cancel' - assert_select 'a[href=/issues/2]' - end - end - - def test_update_stale_issue_should_save_attachments - set_tmp_attachments_directory - issue = Issue.find(2) - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - assert_no_difference 'TimeEntry.count' do - assert_difference 'Attachment.count' do - put :update, - :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => 'My notes', - :lock_version => (issue.lock_version - 1) - }, - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}, - :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id } - end - end - end - - assert_response :success - 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/ - end - - def test_update_stale_issue_without_notes_should_not_show_add_notes_option - issue = Issue.find(2) - @request.session[:user_id] = 2 - - put :update, :id => issue.id, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => (issue.lock_version - 1) - } - - assert_tag 'div', :attributes => {:class => 'conflict'} - assert_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'overwrite'} - assert_no_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'add_notes'} - assert_tag 'input', :attributes => {:name => 'conflict_resolution', :value => 'cancel'} - end - - def test_update_stale_issue_should_show_conflicting_journals - @request.session[:user_id] = 2 - - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => 2 - }, - :last_journal_id => 1 - - assert_not_nil assigns(:conflict_journals) - assert_equal 1, assigns(:conflict_journals).size - assert_equal 2, assigns(:conflict_journals).first.id - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Some notes with Redmine links/} - end - - def test_update_stale_issue_without_previous_journal_should_show_all_journals - @request.session[:user_id] = 2 - - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => '', - :lock_version => 2 - }, - :last_journal_id => '' - - assert_not_nil assigns(:conflict_journals) - assert_equal 2, assigns(:conflict_journals).size - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Some notes with Redmine links/} - assert_tag 'div', :attributes => {:class => 'conflict'}, - :descendant => {:content => /Journal notes/} - end - - def test_update_stale_issue_should_show_private_journals_with_permission_only - journal = Journal.create!(:journalized => Issue.find(1), :notes => 'Privates notes', :private_notes => true, :user_id => 1) - - @request.session[:user_id] = 2 - put :update, :id => 1, :issue => {:fixed_version_id => 4, :lock_version => 2}, :last_journal_id => '' - assert_include journal, assigns(:conflict_journals) - - Role.find(1).remove_permission! :view_private_notes - put :update, :id => 1, :issue => {:fixed_version_id => 4, :lock_version => 2}, :last_journal_id => '' - assert_not_include journal, assigns(:conflict_journals) - end - - def test_update_stale_issue_with_overwrite_conflict_resolution_should_update - @request.session[:user_id] = 2 - - assert_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'overwrite_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'overwrite' - end - - assert_response 302 - issue = Issue.find(1) - assert_equal 4, issue.fixed_version_id - journal = Journal.first(:order => 'id DESC') - assert_equal 'overwrite_conflict_resolution', journal.notes - assert journal.details.any? - end - - def test_update_stale_issue_with_add_notes_conflict_resolution_should_update - @request.session[:user_id] = 2 - - assert_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'add_notes_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'add_notes' - end - - assert_response 302 - issue = Issue.find(1) - assert_nil issue.fixed_version_id - journal = Journal.first(:order => 'id DESC') - assert_equal 'add_notes_conflict_resolution', journal.notes - assert journal.details.empty? - end - - def test_update_stale_issue_with_cancel_conflict_resolution_should_redirect_without_updating - @request.session[:user_id] = 2 - - assert_no_difference 'Journal.count' do - put :update, :id => 1, - :issue => { - :fixed_version_id => 4, - :notes => 'add_notes_conflict_resolution', - :lock_version => 2 - }, - :conflict_resolution => 'cancel' - end - - assert_redirected_to '/issues/1' - issue = Issue.find(1) - assert_nil issue.fixed_version_id - end - - def test_put_update_with_spent_time_and_failure_should_not_add_spent_time - @request.session[:user_id] = 2 - - assert_no_difference('TimeEntry.count') do - put :update, - :id => 1, - :issue => { :subject => '' }, - :time_entry => { :hours => '2.5', :comments => 'should not be added', :activity_id => TimeEntryActivity.first.id } - assert_response :success - end - - assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2.5' - assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'should not be added' - assert_select 'select[name=?]', 'time_entry[activity_id]' do - assert_select 'option[value=?][selected=selected]', TimeEntryActivity.first.id - end - end - - def test_index_should_rescue_invalid_sql_query - Query.any_instance.stubs(:statement).returns("INVALID STATEMENT") - - get :index - assert_response 500 - assert_tag 'p', :content => /An error occurred/ - assert_nil session[:query] - assert_nil session[:issues_index_sort] - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0e/0e6d154c5ead84b5d126f01a8ce10bcd16d83cbd.svn-base --- a/.svn/pristine/0e/0e6d154c5ead84b5d126f01a8ce10bcd16d83cbd.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0e/0ef99b09fb86deecc2f7d49dd96b01429edf06cb.svn-base Binary file .svn/pristine/0e/0ef99b09fb86deecc2f7d49dd96b01429edf06cb.svn-base has changed diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/0f/0f05e49d9c9481ce6698da320e7a0f815ec9a184.svn-base --- a/.svn/pristine/0f/0f05e49d9c9481ce6698da320e7a0f815ec9a184.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,456 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 " => "

    <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 -->

    ", + " -

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

    - - -
    <%=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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a1/a1714901d6819a8b467120ed258d4fc0049a8e19.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a1/a1714901d6819a8b467120ed258d4fc0049a8e19.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,522 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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).downcase.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' + + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + MantisMigrate.establish_connection db_params + MantisMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end + +end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a1/a1b77b806397cc5fdef1b8ec65e5b9652bc154db.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a1/a1b77b806397cc5fdef1b8ec65e5b9652bc154db.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,201 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class MyController < ApplicationController + before_filter :require_login + # let user change user's password when user has to + skip_before_filter :check_password_change, :only => :password + + helper :issues + helper :users + helper :custom_fields + + BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, + 'issuesreportedbyme' => :label_reported_issues, + 'issueswatched' => :label_watched_issues, + 'news' => :label_news_latest, + 'calendar' => :label_calendar, + 'documents' => :label_document_plural, + 'timelog' => :label_spent_time + }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze + + DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], + 'right' => ['issuesreportedbyme'] + }.freeze + + def index + page + render :action => 'page' + end + + # Show user's page + def page + @user = User.current + @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT + end + + # Edit user's account + def account + @user = User.current + @pref = @user.pref + if request.post? + @user.safe_attributes = params[:user] + @user.pref.attributes = params[:pref] + if @user.save + @user.pref.save + set_language_if_valid @user.language + flash[:notice] = l(:notice_account_updated) + redirect_to my_account_path + return + end + end + end + + # Destroys user's account + def destroy + @user = User.current + unless @user.own_account_deletable? + redirect_to my_account_path + return + end + + if request.post? && params[:confirm] + @user.destroy + if @user.destroyed? + logout_user + flash[:notice] = l(:notice_account_deleted) + end + redirect_to home_path + end + end + + # Manage user's password + def password + @user = User.current + unless @user.change_password_allowed? + flash[:error] = l(:notice_can_t_change_password) + redirect_to my_account_path + return + end + if request.post? + if !@user.check_password?(params[:password]) + flash.now[:error] = l(:notice_account_wrong_password) + elsif params[:password] == params[:new_password] + flash.now[:error] = l(:notice_new_password_must_be_different) + else + @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] + @user.must_change_passwd = false + if @user.save + flash[:notice] = l(:notice_account_password_updated) + redirect_to my_account_path + end + end + end + end + + # Create a new feeds key + def reset_rss_key + if request.post? + if User.current.rss_token + User.current.rss_token.destroy + User.current.reload + end + User.current.rss_key + flash[:notice] = l(:notice_feeds_access_key_reseted) + end + redirect_to my_account_path + end + + # Create a new API key + def reset_api_key + if request.post? + if User.current.api_token + User.current.api_token.destroy + User.current.reload + end + User.current.api_key + flash[:notice] = l(:notice_api_access_key_reseted) + end + redirect_to my_account_path + end + + # User's page layout configuration + def page_layout + @user = User.current + @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup + @block_options = [] + BLOCKS.each do |k, v| + unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)} + @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize] + end + end + end + + # Add a block to user's page + # The block is added on top of the page + # params[:block] : id of the block to add + def add_block + block = params[:block].to_s.underscore + if block.present? && BLOCKS.key?(block) + @user = User.current + layout = @user.pref[:my_page_layout] || {} + # remove if already present in a group + %w(top left right).each {|f| (layout[f] ||= []).delete block } + # add it on top + layout['top'].unshift block + @user.pref[:my_page_layout] = layout + @user.pref.save + end + redirect_to my_page_layout_path + end + + # Remove a block to user's page + # params[:block] : id of the block to remove + def remove_block + block = params[:block].to_s.underscore + @user = User.current + # remove block in all groups + layout = @user.pref[:my_page_layout] || {} + %w(top left right).each {|f| (layout[f] ||= []).delete block } + @user.pref[:my_page_layout] = layout + @user.pref.save + redirect_to my_page_layout_path + end + + # Change blocks order on user's page + # params[:group] : group to order (top, left or right) + # params[:list-(top|left|right)] : array of block ids of the group + def order_blocks + group = params[:group] + @user = User.current + if group.is_a?(String) + group_items = (params["blocks"] || []).collect(&:underscore) + group_items.each {|s| s.sub!(/^block_/, '')} + if group_items and group_items.is_a? Array + layout = @user.pref[:my_page_layout] || {} + # remove group blocks if they are presents in other groups + %w(top left right).each {|f| + layout[f] = (layout[f] || []) - group_items + } + layout[group] = group_items + @user.pref[:my_page_layout] = layout + @user.pref.save + end + end + render :nothing => true + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a2/a22c9b7663ad593c48eebef0aad08af8d08a2dc8.svn-base --- a/.svn/pristine/a2/a22c9b7663ad593c48eebef0aad08af8d08a2dc8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::IssueRelationsTest < Redmine::ApiTest::Base - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :issue_relations - - def setup - Setting.rest_api_enabled = '1' - end - - context "/issues/:issue_id/relations" do - context "GET" do - should "return issue relations" do - get '/issues/9/relations.xml', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag :tag => 'relations', - :attributes => { :type => 'array' }, - :child => { - :tag => 'relation', - :child => { - :tag => 'id', - :content => '1' - } - } - end - end - - context "POST" do - should "create a relation" do - assert_difference('IssueRelation.count') do - post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, credentials('jsmith') - end - - relation = IssueRelation.first(:order => 'id DESC') - assert_equal 2, relation.issue_from_id - assert_equal 7, relation.issue_to_id - assert_equal 'relates', relation.relation_type - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'relation', :child => {:tag => 'id', :content => relation.id.to_s} - end - - context "with failure" do - should "return the errors" do - assert_no_difference('IssueRelation.count') do - post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, credentials('jsmith') - end - - assert_response :unprocessable_entity - assert_tag :errors, :child => {:tag => 'error', :content => /relation_type is not included in the list/} - end - end - end - end - - context "/relations/:id" do - context "GET" do - should "return the relation" do - get '/relations/2.xml', {}, credentials('jsmith') - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag 'relation', :child => {:tag => 'id', :content => '2'} - end - end - - context "DELETE" do - should "delete the relation" do - assert_difference('IssueRelation.count', -1) do - delete '/relations/2.xml', {}, credentials('jsmith') - end - - assert_response :ok - assert_equal '', @response.body - assert_nil IssueRelation.find_by_id(2) - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a2/a268aadf60b30880792c7f50cf7c1f07f63819c9.svn-base --- a/.svn/pristine/a2/a268aadf60b30880792c7f50cf7c1f07f63819c9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::JsonpTest < ActionController::IntegrationTest - fixtures :trackers - - def test_jsonp_should_accept_callback_param - get '/trackers.json?callback=handler' - - assert_response :success - assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_should_accept_jsonp_param - get '/trackers.json?jsonp=handler' - - assert_response :success - assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_should_strip_invalid_characters_from_callback - get '/trackers.json?callback=+-aA$1_' - - assert_response :success - assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body - assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] - end - - def test_jsonp_without_callback_should_return_json - get '/trackers.json?callback=' - - assert_response :success - assert_match %r{^\{"trackers":.+\}$}, response.body - assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a314ca86d401cd6c66c2c275d0d98bd9585e53c9.svn-base --- a/.svn/pristine/a3/a314ca86d401cd6c66c2c275d0d98bd9585e53c9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module ProjectsHelper - def link_to_version(version, options = {}) - return '' unless version && version.is_a?(Version) - link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options - end - - def project_settings_tabs - tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, - {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, - {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, - {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, - {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural}, - {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, - {:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural}, - {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, - {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} - ] - tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} - end - - def parent_project_select_tag(project) - selected = project.parent - # retrieve the requested parent project - parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id] - if parent_id - selected = (parent_id.blank? ? nil : Project.find(parent_id)) - end - - options = '' - options << "" if project.allowed_parents.include?(nil) - options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) - content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id') - end - - # Renders the projects index - def render_project_hierarchy(projects) - render_project_nested_lists(projects) do |project| - s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}") - if project.description.present? - s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description') - end - s - end - end - - # Returns a set of options for a select field, grouped by project. - def version_options_for_select(versions, selected=nil) - grouped = Hash.new {|h,k| h[k] = []} - versions.each do |version| - grouped[version.project.name] << [version.name, version.id] - end - - if grouped.keys.size > 1 - grouped_options_for_select(grouped, selected && selected.id) - else - options_for_select((grouped.values.first || []), selected && selected.id) - end - end - - def format_version_sharing(sharing) - sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) - l("label_version_sharing_#{sharing}") - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a326dc11b9640e3425d44fc1fc230394f2aa2cdc.svn-base --- a/.svn/pristine/a3/a326dc11b9640e3425d44fc1fc230394f2aa2cdc.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class ChangeChangesetsRevisionToString < ActiveRecord::Migration - def self.up - change_column :changesets, :revision, :string, :null => false - end - - def self.down - change_column :changesets, :revision, :integer, :null => false - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a39dfd4bdc71ffef44da946a57f879f5baad33bc.svn-base --- a/.svn/pristine/a3/a39dfd4bdc71ffef44da946a57f879f5baad33bc.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,831 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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] - @inline = options.key?(:inline) ? options[:inline] : true - @caption_key = options[:caption] || "field_#{name}".to_sym - @frozen = options[:frozen] - end - - def caption - @caption_key.is_a?(Symbol) ? l(@caption_key) : @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 inline? - @inline - end - - def frozen? - @frozen - end - - def value(object) - object.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 - self.groupable = custom_field.group_statement || false - @inline = true - @cf = custom_field - end - - def caption - @cf.name - end - - def custom_field - @cf - end - - 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 - - def css_classes - @css_classes ||= "#{name} #{@cf.field_format}" - 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 - - 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 - validates_length_of :name, :maximum => 255 - validate :validate_query_filters - - 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 - } - - 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", "!*", "*"] - } - - class_attribute :available_columns - self.available_columns = [] - - class_attribute :queried_class - - def queried_table_name - @queried_table_name ||= self.class.queried_class.table_name - end - - def initialize(attributes=nil, *args) - super attributes - @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) - case type_for(field) - when :integer - add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) } - when :float - add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) } - when :date, :date_past - case operator_for(field) - when "=", ">=", "<=", "><" - add_filter_error(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-", "t+", " 'activerecord.errors.messages') - errors.add(:base, m) - end - - def editable_by?(user) - return false unless user - # Admin can edit them all and regular users can edit their private queries - return true if user.admin? || (!is_public && self.user_id == user.id) - # Members can not edit public queries that are for all project (only admin is allowed to) - is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) - end - - def 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 - - # Returns a representation of the available filters for JSON serialization - def available_filters_as_json - json = {} - available_filters.each do |field, options| - json[field] = options.slice(:type, :name, :values).stringify_keys - end - json - end - - def all_projects - @all_projects ||= Project.visible.all - end - - def all_projects_values - return @all_projects_values if @all_projects_values - - values = [] - Project.project_tree(all_projects) do |p, level| - prefix = (level > 0 ? ('--' * level + ' ') : '') - values << ["#{prefix}#{p.name}", p.id.to_s] - end - @all_projects_values = values - end - - # 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] - 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)}(.*)$/ - values = $1 - add_filter field, operator, values.present? ? values.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 ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) - 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 - available_columns.inject({}) {|h, column| - h[column.name.to_s] = column.sortable - h - } - end - - def columns - # preserve the column_names order - 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 - columns.select(&:inline?) - end - - def block_columns - columns.reject(&:inline?) - end - - def available_inline_columns - available_columns.select(&:inline?) - end - - def available_block_columns - available_columns.reject(&:inline?) - end - - def default_columns_names - [] - 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.is_a?(QueryColumn) ? column.name : column) - 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 == false) ? 'desc' : '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 - - def sort_criteria_order_for(key) - sort_criteria.detect {|k, order| key.to_s == k}.try(:last) - end - - # Returns the SQL sort order that should be prepended for grouping - def group_by_sort_order - if grouped? && (column = group_by_column) - order = sort_criteria_order_for(column.name) || column.default_order - column.sortable.is_a?(Array) ? - column.sortable.collect {|s| "#{s} #{order}"}.join(',') : - "#{column.sortable} #{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 user_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 == 'project_id' - if v.delete('mine') - v += User.current.memberships.map(&:project_id).map(&:to_s) - 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, queried_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 - - private - - def sql_for_custom_field(field, operator, value, custom_field_id) - db_table = CustomValue.table_name - db_field = 'value' - filter = @available_filters[field] - return nil unless filter - if filter[:format] == 'user' - if value.delete('me') - value.push User.current.id.to_s - end - end - not_in = nil - if operator == '!' - # Makes ! operator work for custom fields with multiple values - operator = '=' - not_in = 'NOT' - end - customized_key = "id" - customized_class = queried_class - if field =~ /^(.+)\.cf_/ - assoc = $1 - customized_key = "#{assoc}_id" - 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 - 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+ - 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 - if is_custom_filter - 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(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 - 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 = "(#{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 - 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 = "(#{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 - 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 = "(#{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 = "#{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 = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" - when ">t-" - # >= today - n days - sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil) - when "t+" - # >= today + n days - 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 "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 "!~" - 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, assoc=nil) - return unless custom_fields.present? - - custom_fields.select(&:is_filter?).sort.each do |field| - case field.field_format - when "text" - options = { :type => :text } - when "list" - options = { :type => :list_optional, :values => field.possible_values } - when "date" - options = { :type => :date } - when "bool" - options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] } - when "int" - options = { :type => :integer } - when "float" - 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 } - else - options = { :type => :string } - end - filter_id = "cf_#{field.id}" - filter_name = field.name - if assoc.present? - filter_id = "#{assoc}.#{filter_id}" - filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) - end - add_available_filter filter_id, options.merge({ - :name => filter_name, - :format => field.field_format, - :field => field - }) - end - end - - def add_associations_custom_fields_filters(*associations) - fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) - associations.each do |assoc| - 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) - end - end - 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_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day) - if self.class.default_timezone == :utc - from_yesterday_time = from_yesterday_time.utc - end - s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) - end - if to - to_time = Time.local(to.year, to.month, to.day) - if self.class.default_timezone == :utc - to_time = to_time.utc - end - s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.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 - - # Additional joins required for the given sort options - def joins_for_order_statement(order_options) - joins = [] - - if order_options - if order_options.include?('authors') - 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} - join = column && column.custom_field.join_for_order_statement - if join - joins << join - end - end - end - - joins.any? ? joins.join(' ') : nil - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a3a57388b3ae4f50b030906e001d30172793704b.svn-base --- a/.svn/pristine/a3/a3a57388b3ae4f50b030906e001d30172793704b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, :users, :groups_users - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a3a58ab9f9fc5bc4302fa7dbcd592c4ba267cc9b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a3/a3a58ab9f9fc5bc4302fa7dbcd592c4ba267cc9b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, :trackers + + def test_sorted_scope + assert_equal Role.all.sort, Role.sorted.all + end + + def test_givable_scope + assert_equal Role.all.reject(&:builtin?).sort, Role.givable.all + end + + def test_builtin_scope + assert_equal Role.all.select(&:builtin?).sort, Role.builtin(true).all.sort + assert_equal Role.all.reject(&:builtin?).sort, Role.builtin(false).all.sort + end + + def test_copy_from + role = Role.find(1) + copy = Role.new.copy_from(role) + + assert_nil copy.id + assert_equal '', copy.name + assert_equal role.permissions, copy.permissions + + copy.name = 'Copy' + assert copy.save + end + + def test_copy_workflows + source = Role.find(1) + assert_equal 90, source.workflow_rules.size + + target = Role.new(:name => 'Target') + assert target.save + target.workflow_rules.copy(source) + target.reload + assert_equal 90, target.workflow_rules.size + end + + def test_permissions_should_be_unserialized_with_its_coder + Role::PermissionsAttributeCoder.expects(:load).once + Role.find(1).permissions + 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 + + def test_find_all_givable + assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable + end + + def test_anonymous_should_return_the_anonymous_role + assert_no_difference('Role.count') do + role = Role.anonymous + assert role.builtin? + assert_equal Role::BUILTIN_ANONYMOUS, role.builtin + end + end + + def test_anonymous_with_a_missing_anonymous_role_should_return_the_anonymous_role + Role.where(:builtin => Role::BUILTIN_ANONYMOUS).delete_all + + assert_difference('Role.count') do + role = Role.anonymous + assert role.builtin? + assert_equal Role::BUILTIN_ANONYMOUS, role.builtin + end + end + + def test_non_member_should_return_the_non_member_role + assert_no_difference('Role.count') do + role = Role.non_member + assert role.builtin? + assert_equal Role::BUILTIN_NON_MEMBER, role.builtin + end + end + + def test_non_member_with_a_missing_non_member_role_should_return_the_non_member_role + Role.where(:builtin => Role::BUILTIN_NON_MEMBER).delete_all + + assert_difference('Role.count') do + role = Role.non_member + assert role.builtin? + assert_equal Role::BUILTIN_NON_MEMBER, role.builtin + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a3b49dd1167634bbf9c5460ad4e1ffc52389846f.svn-base --- a/.svn/pristine/a3/a3b49dd1167634bbf9c5460ad4e1ffc52389846f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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(this); 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'), 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 -%> -
    -
    -<% end -%> - -<%= context_menu time_entries_context_menu_path %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a3b9e1f5db2062f981afc156975b07fe4b88affe.svn-base --- a/.svn/pristine/a3/a3b9e1f5db2062f981afc156975b07fe4b88affe.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -class AddSettingsUpdatedOn < ActiveRecord::Migration - def self.up - add_column :settings, :updated_on, :timestamp - # set updated_on - Setting.find(:all).each(&:save) - end - - def self.down - remove_column :settings, :updated_on - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a3/a3be1d86be827009cae521df133b9bd67e4c3929.svn-base --- a/.svn/pristine/a3/a3be1d86be827009cae521df133b9bd67e4c3929.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +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; - } -} - -// OK -function selectAllOptions(id) -{ - var select = $('#'+id);/* - for (var i=0; i "status_by_form") do -%> -
    - -<%= l(:label_issues_by, - select_tag('status_by', - status_by_options_for_select(criteria), - :id => 'status_by_select', - :data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %> - -<% if counts.empty? %> -

    <%= l(:label_no_data) %>

    -<% else %> - - <% counts.each do |count| %> - - - - - <% end %> -
    - <% if count[:group] -%> - <%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %> - <% else -%> - <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %> - <% end %> - - <%= progress_bar((count[:closed].to_f / count[:total])*100, - :legend => "#{count[:closed]}/#{count[:total]}", - :width => "#{(count[:total].to_f / max * 200).floor}px;") %> -
    -<% end %> -
    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a4/a4c45a7af3a5e670c765d740cec096dac5d5314d.svn-base --- a/.svn/pristine/a4/a4c45a7af3a5e670c765d740cec096dac5d5314d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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, 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a50b7b77054746ae04a29c1d25d50e5dbb10bf4e.svn-base --- a/.svn/pristine/a5/a50b7b77054746ae04a29c1d25d50e5dbb10bf4e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1089 +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_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: 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: "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: 'Greek (Ελληνικά)' - 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}." - - - 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_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_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_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_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}" - 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 - 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: 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: Σύνολο - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a50c8a4fc61822f00307fa81cf3630d9ac86b4c0.svn-base --- a/.svn/pristine/a5/a50c8a4fc61822f00307fa81cf3630d9ac86b4c0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,208 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a54694ad491e752b7f87503a37270f988e6883b0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a54694ad491e752b7f87503a37270f988e6883b0.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 MenuManagerTest < ActionController::IntegrationTest + include Redmine::I18n + + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :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' + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_activity), + :attributes => { :href => '/projects/ecookbook/activity', + :class => 'activity' } } } + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_issue_plural), + :attributes => { :href => '/projects/ecookbook/issues', + :class => 'issues selected' } } } + end + + def test_project_menu_with_additional_menu_items + Setting.default_language = 'en' + assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do + Redmine::MenuManager.map :project_menu do |menu| + menu.push :foo, { :controller => 'projects', :action => 'show' }, :caption => 'Foo' + menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity + menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar + end + + get 'projects/ecookbook' + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo', + :attributes => { :class => 'foo' } } } + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar', + :attributes => { :class => 'bar' } }, + :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } } + + assert_tag :div, :attributes => { :id => 'main-menu' }, + :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK', + :attributes => { :class => 'hello' } }, + :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } } + + # Remove the menu items + Redmine::MenuManager.map :project_menu do |menu| + menu.delete :foo + menu.delete :bar + menu.delete :hello + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5718c7636f7768b70cb057f46ace176323e391e.svn-base --- a/.svn/pristine/a5/a5718c7636f7768b70cb057f46ace176323e391e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1100 +0,0 @@ -# Danish translation file for standard Ruby on Rails internationalization -# by Lars Hoeg (http://www.lenio.dk/) -# updated and upgraded to 0.9 by Morten Krogh Andersen (http://www.krogh.net) - -da: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%e. %b %Y" - long: "%e. %B %Y" - - day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag] - abbr_day_names: [sø, ma, ti, 'on', to, fr, lø] - month_names: [~, januar, februar, marts, april, maj, juni, juli, august, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%e. %B %Y, %H:%M" - time: "%H:%M" - short: "%e. %b %Y, %H:%M" - long: "%A, %e. %B %Y, %H:%M" - am: "" - pm: "" - - support: - array: - sentence_connector: "og" - skip_last_comma: true - - datetime: - distance_in_words: - half_a_minute: "et halvt minut" - less_than_x_seconds: - one: "mindre end et sekund" - other: "mindre end %{count} sekunder" - x_seconds: - one: "et sekund" - other: "%{count} sekunder" - less_than_x_minutes: - one: "mindre end et minut" - other: "mindre end %{count} minutter" - x_minutes: - one: "et minut" - other: "%{count} minutter" - about_x_hours: - one: "cirka en time" - other: "cirka %{count} timer" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "en dag" - other: "%{count} dage" - about_x_months: - one: "cirka en måned" - other: "cirka %{count} måneder" - x_months: - one: "en måned" - other: "%{count} måneder" - about_x_years: - one: "cirka et år" - other: "cirka %{count} år" - over_x_years: - one: "mere end et år" - other: "mere end %{count} år" - almost_x_years: - one: "næsten 1 år" - other: "næsten %{count} år" - - number: - format: - separator: "," - delimiter: "." - precision: 3 - currency: - format: - format: "%u %n" - unit: "DKK" - separator: "," - delimiter: "." - precision: 2 - 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" - percentage: - format: - # separator: - delimiter: "" - # precision: - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "er ikke i listen" - exclusion: "er reserveret" - invalid: "er ikke gyldig" - confirmation: "stemmer ikke overens" - accepted: "skal accepteres" - empty: "må ikke udelades" - blank: "skal udfyldes" - too_long: "er for lang (højst %{count} tegn)" - too_short: "er for kort (mindst %{count} tegn)" - wrong_length: "har forkert længde (skulle være %{count} tegn)" - taken: "er allerede anvendt" - not_a_number: "er ikke et tal" - greater_than: "skal være større end %{count}" - greater_than_or_equal_to: "skal være større end eller lig med %{count}" - equal_to: "skal være lig med %{count}" - less_than: "skal være mindre end %{count}" - less_than_or_equal_to: "skal være mindre end eller lig med %{count}" - odd: "skal være ulige" - even: "skal være lige" - greater_than_start_date: "skal være senere end startdatoen" - not_same_project: "hører ikke til samme projekt" - circular_dependency: "Denne relation vil skabe et afhængighedsforhold" - cant_link_an_issue_with_a_descendant: "En sag kan ikke relateres til en af dens underopgaver" - - template: - header: - one: "En fejl forhindrede %{model} i at blive gemt" - other: "%{count} fejl forhindrede denne %{model} i at blive gemt" - body: "Der var problemer med følgende felter:" - - actionview_instancetag_blank_option: Vælg venligst - - general_text_No: 'Nej' - general_text_Yes: 'Ja' - general_text_no: 'nej' - general_text_yes: 'ja' - general_lang_name: 'Danish (Dansk)' - general_csv_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Kontoen er opdateret. - notice_account_invalid_creditentials: Ugyldig bruger og/eller kodeord - notice_account_password_updated: Kodeordet er opdateret. - notice_account_wrong_password: Forkert kodeord - notice_account_register_done: Kontoen er oprettet. For at aktivere kontoen skal du klikke på linket i den tilsendte email. - notice_account_unknown_email: Ukendt bruger. - notice_can_t_change_password: Denne konto benytter en ekstern sikkerhedsgodkendelse. Det er ikke muligt at skifte kodeord. - notice_account_lost_email_sent: En email med instruktioner til at vælge et nyt kodeord er afsendt til dig. - notice_account_activated: Din konto er aktiveret. Du kan nu logge ind. - notice_successful_create: Succesfuld oprettelse. - notice_successful_update: Succesfuld opdatering. - notice_successful_delete: Succesfuld sletning. - notice_successful_connection: Succesfuld forbindelse. - notice_file_not_found: Siden du forsøger at tilgå eksisterer ikke eller er blevet fjernet. - notice_locking_conflict: Data er opdateret af en anden bruger. - notice_not_authorized: Du har ikke adgang til denne side. - notice_email_sent: "En email er sendt til %{value}" - notice_email_error: "En fejl opstod under afsendelse af email (%{value})" - notice_feeds_access_key_reseted: Din adgangsnøgle til RSS er nulstillet. - notice_failed_to_save_issues: "Det mislykkedes at gemme %{count} sage(r) på %{total} valgt: %{ids}." - notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst hvilke emner du vil rette." - notice_account_pending: "Din konto er oprettet, og afventer administrators godkendelse." - notice_default_data_loaded: Standardopsætningen er indlæst. - - error_can_t_load_default_data: "Standardopsætning kunne ikke indlæses: %{value}" - error_scm_not_found: "Adgang nægtet og/eller revision blev ikke fundet i det valgte repository." - error_scm_command_failed: "En fejl opstod under forbindelsen til det valgte repository: %{value}" - - mail_subject_lost_password: "Dit %{value} kodeord" - mail_body_lost_password: 'Klik på dette link for at ændre dit kodeord:' - mail_subject_register: "%{value} kontoaktivering" - mail_body_register: 'Klik på dette link for at aktivere din konto:' - mail_body_account_information_external: "Du kan bruge din %{value} konto til at logge ind." - mail_body_account_information: Din kontoinformation - 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 - field_summary: Sammenfatning - field_is_required: Skal udfyldes - field_firstname: Fornavn - field_lastname: Efternavn - field_mail: Email - field_filename: Fil - field_filesize: Størrelse - field_downloads: Downloads - field_author: Forfatter - field_created_on: Oprettet - field_updated_on: Opdateret - field_field_format: Format - field_is_for_all: For alle projekter - field_possible_values: Mulige værdier - field_regexp: Regulære udtryk - field_min_length: Mindste længde - field_max_length: Største længde - field_value: Værdi - field_category: Kategori - field_title: Titel - field_project: Projekt - field_issue: Sag - field_status: Status - field_notes: Noter - field_is_closed: Sagen er lukket - field_is_default: Standardværdi - field_tracker: Type - field_subject: Emne - field_due_date: Deadline - field_assigned_to: Tildelt til - field_priority: Prioritet - field_fixed_version: Udgave - field_user: Bruger - field_role: Rolle - field_homepage: Hjemmeside - field_is_public: Offentlig - field_parent: Underprojekt af - field_is_in_roadmap: Sager vist i roadmap - field_login: Login - field_mail_notification: Email-påmindelser - field_admin: Administrator - field_last_login_on: Sidste forbindelse - field_language: Sprog - field_effective_date: Dato - field_password: Kodeord - field_new_password: Nyt kodeord - field_password_confirmation: Bekræft - field_version: Version - field_type: Type - field_host: Vært - field_port: Port - field_account: Kode - field_base_dn: Base DN - field_attr_login: Login attribut - field_attr_firstname: Fornavn attribut - field_attr_lastname: Efternavn attribut - field_attr_mail: Email attribut - field_onthefly: løbende brugeroprettelse - field_start_date: Start dato - field_done_ratio: "% færdig" - field_auth_source: Sikkerhedsmetode - field_hide_mail: Skjul min email - field_comments: Kommentar - field_url: URL - field_start_page: Startside - field_subproject: Underprojekt - field_hours: Timer - field_activity: Aktivitet - field_spent_on: Dato - field_identifier: Identifikator - field_is_filter: Brugt som et filter - field_issue_to: Beslægtede sag - field_delay: Udsættelse - field_assignable: Sager kan tildeles denne rolle - field_redirect_existing_links: Videresend eksisterende links - field_estimated_hours: Anslået tid - field_column_names: Kolonner - field_time_zone: Tidszone - field_searchable: Søgbar - field_default_value: Standardværdi - - setting_app_title: Applikationstitel - setting_app_subtitle: Applikationsundertekst - setting_welcome_text: Velkomsttekst - setting_default_language: Standardsporg - setting_login_required: Sikkerhed påkrævet - setting_self_registration: Brugeroprettelse - setting_attachment_max_size: Vedhæftede filers max størrelse - setting_issues_export_limit: Sagseksporteringsbegrænsning - setting_mail_from: Afsender-email - setting_bcc_recipients: Skjult modtager (bcc) - setting_host_name: Værtsnavn - setting_text_formatting: Tekstformatering - setting_wiki_compression: Komprimering af wiki-historik - setting_feeds_limit: Feed indholdsbegrænsning - setting_autofetch_changesets: Hent automatisk commits - setting_sys_api_enabled: Aktiver webservice for automatisk administration af repository - setting_commit_ref_keywords: Referencenøgleord - setting_commit_fix_keywords: Afslutningsnøgleord - setting_autologin: Automatisk login - setting_date_format: Datoformat - setting_time_format: Tidsformat - setting_cross_project_issue_relations: Tillad sagsrelationer på tværs af projekter - setting_issue_list_default_columns: Standardkolonner på sagslisten - setting_emails_footer: Email-fodnote - setting_protocol: Protokol - setting_user_format: Brugervisningsformat - - project_module_issue_tracking: Sagssøgning - project_module_time_tracking: Tidsstyring - project_module_news: Nyheder - project_module_documents: Dokumenter - project_module_files: Filer - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Fora - - label_user: Bruger - label_user_plural: Brugere - label_user_new: Ny bruger - label_project: Projekt - label_project_new: Nyt projekt - label_project_plural: Projekter - label_x_projects: - zero: Ingen projekter - one: 1 projekt - other: "%{count} projekter" - label_project_all: Alle projekter - label_project_latest: Seneste projekter - label_issue: Sag - label_issue_new: Opret sag - label_issue_plural: Sager - label_issue_view_all: Vis alle sager - label_issues_by: "Sager fra %{value}" - label_issue_added: Sagen er oprettet - label_issue_updated: Sagen er opdateret - label_document: Dokument - label_document_new: Nyt dokument - label_document_plural: Dokumenter - label_document_added: Dokument tilføjet - label_role: Rolle - label_role_plural: Roller - label_role_new: Ny rolle - label_role_and_permissions: Roller og rettigheder - label_member: Medlem - label_member_new: Nyt medlem - label_member_plural: Medlemmer - label_tracker: Type - label_tracker_plural: Typer - label_tracker_new: Ny type - label_workflow: Arbejdsgang - label_issue_status: Sagsstatus - label_issue_status_plural: Sagsstatusser - label_issue_status_new: Ny status - label_issue_category: Sagskategori - label_issue_category_plural: Sagskategorier - label_issue_category_new: Ny kategori - label_custom_field: Brugerdefineret felt - label_custom_field_plural: Brugerdefinerede felter - label_custom_field_new: Nyt brugerdefineret felt - label_enumerations: Værdier - label_enumeration_new: Ny værdi - label_information: Information - label_information_plural: Information - label_please_login: Login - label_register: Registrér - label_password_lost: Glemt kodeord - label_home: Forside - label_my_page: Min side - label_my_account: Min konto - label_my_projects: Mine projekter - label_administration: Administration - label_login: Log ind - label_logout: Log ud - label_help: Hjælp - label_reported_issues: Rapporterede sager - label_assigned_to_me_issues: Sager tildelt til mig - label_last_login: Sidste logintidspunkt - label_registered_on: Registreret den - label_activity: Aktivitet - label_new: Ny - label_logged_as: Registreret som - label_environment: Miljø - label_authentication: Sikkerhed - label_auth_source: Sikkerhedsmetode - label_auth_source_new: Ny sikkerhedsmetode - label_auth_source_plural: Sikkerhedsmetoder - label_subproject_plural: Underprojekter - label_min_max_length: Min - Max længde - label_list: Liste - label_date: Dato - label_integer: Heltal - label_float: Kommatal - label_boolean: Sand/falsk - label_string: Tekst - 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 - label_attachment: Fil - label_attachment_new: Ny fil - label_attachment_delete: Slet fil - label_attachment_plural: Filer - label_file_added: Fil tilføjet - label_report: Rapport - label_report_plural: Rapporter - label_news: Nyheder - label_news_new: Tilføj nyheder - label_news_plural: Nyheder - label_news_latest: Seneste nyheder - label_news_view_all: Vis alle nyheder - label_news_added: Nyhed tilføjet - label_settings: Indstillinger - label_overview: Oversigt - label_version: Udgave - label_version_new: Ny udgave - label_version_plural: Udgaver - label_confirmation: Bekræftelser - label_export_to: Eksporter til - label_read: Læs... - label_public_projects: Offentlige projekter - label_open_issues: åben - label_open_issues_plural: åbne - label_closed_issues: lukket - label_closed_issues_plural: lukkede - label_x_open_issues_abbr_on_total: - zero: 0 åbne / %{total} - one: 1 åben / %{total} - other: "%{count} åbne / %{total}" - label_x_open_issues_abbr: - zero: 0 åbne - one: 1 åben - other: "%{count} åbne" - label_x_closed_issues_abbr: - zero: 0 lukkede - one: 1 lukket - other: "%{count} lukkede" - label_total: Total - label_permissions: Rettigheder - label_current_status: Nuværende status - label_new_statuses_allowed: Ny status tilladt - label_all: alle - label_none: intet - label_nobody: ingen - label_next: Næste - label_previous: Forrige - label_used_by: Brugt af - label_details: Detaljer - label_add_note: Tilføj note - label_per_page: Pr. side - label_calendar: Kalender - label_months_from: måneder frem - label_gantt: Gantt - label_internal: Intern - label_last_changes: "sidste %{count} ændringer" - label_change_view_all: Vis alle ændringer - label_personalize_page: Tilret denne side - label_comment: Kommentar - label_comment_plural: Kommentarer - label_x_comments: - zero: ingen kommentarer - one: 1 kommentar - other: "%{count} kommentarer" - label_comment_add: Tilføj en kommentar - label_comment_added: Kommentaren er tilføjet - label_comment_delete: Slet kommentar - label_query: Brugerdefineret forespørgsel - label_query_plural: Brugerdefinerede forespørgsler - label_query_new: Ny forespørgsel - label_filter_add: Tilføj filter - label_filter_plural: Filtre - label_equals: er - label_not_equals: er ikke - label_in_less_than: er mindre end - label_in_more_than: er større end - label_in: indeholdt i - label_today: i dag - label_all_time: altid - label_yesterday: i går - label_this_week: denne uge - label_last_week: sidste uge - label_last_n_days: "sidste %{count} dage" - label_this_month: denne måned - label_last_month: sidste måned - label_this_year: dette år - label_date_range: Dato interval - label_less_than_ago: mindre end dage siden - label_more_than_ago: mere end dage siden - label_ago: dage siden - label_contains: indeholder - label_not_contains: ikke indeholder - label_day_plural: dage - 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 - label_added: tilføjet - label_modified: ændret - label_deleted: slettet - label_latest_revision: Seneste revision - label_latest_revision_plural: Seneste revisioner - label_view_revisions: Se revisioner - label_max_size: Maksimal størrelse - label_sort_highest: Flyt til toppen - label_sort_higher: Flyt op - label_sort_lower: Flyt ned - label_sort_lowest: Flyt til bunden - label_roadmap: Roadmap - label_roadmap_due_in: Deadline - label_roadmap_overdue: "%{value} forsinket" - label_roadmap_no_issues: Ingen sager i denne version - label_search: Søg - label_result_plural: Resultater - label_all_words: Alle ord - label_wiki: Wiki - label_wiki_edit: Wiki ændring - label_wiki_edit_plural: Wiki ændringer - label_wiki_page: Wiki side - label_wiki_page_plural: Wiki sider - label_index_by_title: Indhold efter titel - label_index_by_date: Indhold efter dato - label_current_version: Nuværende version - label_preview: Forhåndsvisning - label_feed_plural: Feeds - label_changes_details: Detaljer for alle ændringer - label_issue_tracking: Sagssøgning - label_spent_time: Anvendt tid - label_f_hour: "%{value} time" - label_f_hour_plural: "%{value} timer" - label_time_tracking: Tidsstyring - label_change_plural: Ændringer - label_statistics: Statistik - label_commits_per_month: Commits pr. måned - label_commits_per_author: Commits pr. bruger - label_view_diff: Vis forskelle - label_diff_inline: inline - label_diff_side_by_side: side om side - label_options: Formatering - label_copy_workflow_from: Kopier arbejdsgang fra - label_permissions_report: Godkendelsesrapport - label_watched_issues: Overvågede sager - label_related_issues: Relaterede sager - label_applied_status: Anvendte statusser - label_loading: Indlæser... - label_relation_new: Ny relation - label_relation_delete: Slet relation - label_relates_to: relaterer til - label_duplicates: duplikater - label_blocks: blokerer - label_blocked_by: blokeret af - label_precedes: kommer før - label_follows: følger - label_end_to_start: slut til start - label_end_to_end: slut til slut - label_start_to_start: start til start - label_start_to_end: start til slut - label_stay_logged_in: Forbliv indlogget - label_disabled: deaktiveret - label_show_completed_versions: Vis færdige versioner - label_me: mig - label_board: Forum - label_board_new: Nyt forum - label_board_plural: Fora - label_topic_plural: Emner - label_message_plural: Beskeder - label_message_last: Sidste besked - label_message_new: Ny besked - label_message_posted: Besked tilføjet - label_reply_plural: Besvarer - label_send_information: Send konto information til bruger - label_year: År - label_month: Måned - label_week: Uge - label_date_from: Fra - label_date_to: Til - label_language_based: Baseret på brugerens sprog - label_sort_by: "Sortér efter %{value}" - label_send_test_email: Send en test email - label_feeds_access_key_created_on: "RSS adgangsnøgle dannet for %{value} siden" - label_module_plural: Moduler - label_added_time_by: "Tilføjet af %{author} for %{age} siden" - label_updated_time: "Opdateret for %{value} siden" - label_jump_to_a_project: Skift til projekt... - label_file_plural: Filer - label_changeset_plural: Ændringer - label_default_columns: Standardkolonner - label_no_change_option: (Ingen ændringer) - label_bulk_edit_selected_issues: Masse-ret de valgte sager - label_theme: Tema - label_default: standard - label_search_titles_only: Søg kun i titler - label_user_mail_option_all: "For alle hændelser på mine projekter" - label_user_mail_option_selected: "For alle hændelser på de valgte projekter..." - label_user_mail_no_self_notified: "Jeg ønsker ikke besked om ændring foretaget af mig selv" - label_registration_activation_by_email: kontoaktivering på email - label_registration_manual_activation: manuel kontoaktivering - label_registration_automatic_activation: automatisk kontoaktivering - label_display_per_page: "Per side: %{value}" - label_age: Alder - label_change_properties: Ændre indstillinger - label_general: Generelt - label_more: Mere - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP-godkendelse - label_downloads_abbr: D/L - - button_login: Login - button_submit: Send - button_save: Gem - button_check_all: Vælg alt - button_uncheck_all: Fravælg alt - button_delete: Slet - button_create: Opret - button_test: Test - button_edit: Ret - button_add: Tilføj - button_change: Ændre - button_apply: Anvend - button_clear: Nulstil - button_lock: Lås - button_unlock: Lås op - button_download: Download - button_list: List - button_view: Vis - button_move: Flyt - button_back: Tilbage - button_cancel: Annullér - button_activate: Aktivér - button_sort: Sortér - button_log_time: Log tid - button_rollback: Tilbagefør til denne version - button_watch: Overvåg - button_unwatch: Stop overvågning - button_reply: Besvar - button_archive: Arkivér - button_unarchive: Fjern fra arkiv - button_reset: Nulstil - button_rename: Omdøb - button_change_password: Skift kodeord - button_copy: Kopiér - button_annotate: Annotér - button_update: Opdatér - button_configure: Konfigurér - - status_active: aktiv - status_registered: registreret - status_locked: låst - - text_select_mail_notifications: Vælg handlinger der skal sendes email besked for. - text_regexp_info: f.eks. ^[A-ZÆØÅ0-9]+$ - text_min_max_length_info: 0 betyder ingen begrænsninger - text_project_destroy_confirmation: Er du sikker på at du vil slette dette projekt og alle relaterede data? - text_workflow_edit: Vælg en rolle samt en type, for at redigere arbejdsgangen - text_are_you_sure: Er du sikker? - text_tip_issue_begin_day: opgaven begynder denne dag - text_tip_issue_end_day: opaven slutter denne dag - text_tip_issue_begin_end_day: opgaven begynder og slutter denne dag - text_caracters_maximum: "max %{count} karakterer." - text_caracters_minimum: "Skal være mindst %{count} karakterer lang." - text_length_between: "Længde skal være mellem %{min} og %{max} karakterer." - text_tracker_no_workflow: Ingen arbejdsgang defineret for denne type - text_unallowed_characters: Ikke-tilladte karakterer - text_comma_separated: Adskillige værdier tilladt (adskilt med komma). - text_issues_ref_in_commit_messages: Referer og løser sager i commit-beskeder - text_issue_added: "Sag %{id} er rapporteret af %{author}." - text_issue_updated: "Sag %{id} er blevet opdateret af %{author}." - text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wiki og dens indhold? - text_issue_category_destroy_question: "Nogle sager (%{count}) er tildelt denne kategori. Hvad ønsker du at gøre?" - text_issue_category_destroy_assignments: Slet tildelinger af kategori - text_issue_category_reassign_to: Tildel sager til denne kategori - text_user_mail_option: "For ikke-valgte projekter vil du kun modtage beskeder omhandlende ting du er involveret i eller overvåger (f.eks. sager du har indberettet eller ejer)." - text_no_configuration_data: "Roller, typer, sagsstatusser og arbejdsgange er endnu ikke konfigureret.\nDet er anbefalet at indlæse standardopsætningen. Du vil kunne ændre denne når den er indlæst." - text_load_default_configuration: Indlæs standardopsætningen - text_status_changed_by_changeset: "Anvendt i ændring %{value}." - text_issues_destroy_confirmation: 'Er du sikker på du ønsker at slette den/de valgte sag(er)?' - text_select_project_modules: 'Vælg moduler er skal være aktiveret for dette projekt:' - text_default_administrator_account_changed: Standardadministratorkonto ændret - text_file_repository_writable: Filarkiv er skrivbar - text_rmagick_available: RMagick tilgængelig (valgfri) - - default_role_manager: Leder - default_role_developer: Udvikler - default_role_reporter: Rapportør - default_tracker_bug: Fejl - default_tracker_feature: Funktion - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: Igangværende - default_issue_status_resolved: Løst - default_issue_status_feedback: Feedback - default_issue_status_closed: Lukket - default_issue_status_rejected: Afvist - default_doc_category_user: Brugerdokumentation - default_doc_category_tech: Teknisk dokumentation - default_priority_low: Lav - default_priority_normal: Normal - default_priority_high: Høj - default_priority_urgent: Akut - default_priority_immediate: Omgående - default_activity_design: Design - default_activity_development: Udvikling - - enumeration_issue_priorities: Sagsprioriteter - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsstyring) - - label_add_another_file: Tilføj endnu en fil - label_chronological_order: I kronologisk rækkefølge - setting_activity_days_default: Antal dage der vises under projektaktivitet - text_destroy_time_entries_question: "%{hours} timer er registreret på denne sag som du er ved at slette. Hvad vil du gøre?" - error_issue_not_found_in_project: 'Sagen blev ikke fundet eller tilhører ikke dette projekt' - text_assign_time_entries_to_project: Tildel raporterede timer til projektet - setting_display_subprojects_issues: Vis sager for underprojekter på hovedprojektet som standard - label_optional_description: Valgfri beskrivelse - text_destroy_time_entries: Slet registrerede timer - field_comments_sorting: Vis kommentar - text_reassign_time_entries: 'Tildel registrerede timer til denne sag igen' - label_reverse_chronological_order: I omvendt kronologisk rækkefølge - label_preferences: Præferencer - label_overall_activity: Overordnet aktivitet - setting_default_projects_public: Nye projekter er offentlige som standard - error_scm_annotate: "Filen findes ikke, eller kunne ikke annoteres." - label_planning: Planlægning - text_subprojects_destroy_warning: "Dets underprojekter(er): %{value} vil også blive slettet." - permission_edit_issues: Redigér sager - setting_diff_max_lines_displayed: Højeste antal forskelle der vises - permission_edit_own_issue_notes: Redigér egne noter - setting_enabled_scm: Aktiveret SCM - button_quote: Citér - permission_view_files: Se filer - permission_add_issues: Tilføj sager - permission_edit_own_messages: Redigér egne beskeder - permission_delete_own_messages: Slet egne beskeder - permission_manage_public_queries: Administrér offentlig forespørgsler - permission_log_time: Registrér anvendt tid - label_renamed: omdøbt - label_incoming_emails: Indkommende emails - permission_view_changesets: Se ændringer - permission_manage_versions: Administrér versioner - permission_view_time_entries: Se anvendt tid - label_generate_key: Generér en nøglefil - permission_manage_categories: Administrér sagskategorier - permission_manage_wiki: Administrér wiki - setting_sequential_project_identifiers: Generér sekventielle projekt-identifikatorer - setting_plain_text_mail: Emails som almindelig tekst (ingen HTML) - 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 - text_enumeration_category_reassign_to: 'Flyt dem til denne værdi:' - permission_select_project_modules: Vælg projektmoduler - permission_view_gantt: Se Gantt diagram - permission_delete_messages: Slet beskeder - permission_move_issues: Flyt sager - permission_edit_wiki_pages: Redigér wiki sider - label_user_activity: "%{value}'s aktivitet" - permission_manage_issue_relations: Administrér sags-relationer - label_issue_watchers: Overvågere - permission_delete_wiki_pages: Slet wiki sider - notice_unable_delete_version: Kan ikke slette versionen. - permission_view_wiki_edits: Se wiki historik - field_editable: Redigérbar - label_duplicated_by: dubleret af - permission_manage_boards: Administrér fora - permission_delete_wiki_pages_attachments: Slet filer vedhæftet wiki sider - permission_view_messages: Se beskeder - text_enumeration_destroy_question: "%{count} objekter er tildelt denne værdi." - permission_manage_files: Administrér filer - permission_add_messages: Opret beskeder - permission_edit_issue_notes: Redigér noter - permission_manage_news: Administrér nyheder - text_plugin_assets_writable: Der er skriverettigheder til plugin assets folderen - label_display: Vis - label_and_its_subprojects: "%{value} og dets underprojekter" - permission_view_calendar: Se kalender - button_create_and_continue: Opret og fortsæt - setting_gravatar_enabled: Anvend Gravatar brugerikoner - label_updated_time_by: "Opdateret af %{author} for %{age} siden" - text_diff_truncated: '... Listen over forskelle er blevet afkortet da den overstiger den maksimale størrelse der kan vises.' - text_user_wrote: "%{value} skrev:" - setting_mail_handler_api_enabled: Aktiver webservice for indkomne emails - permission_delete_issues: Slet sager - permission_view_documents: Se dokumenter - permission_browse_repository: Gennemse repository - permission_manage_repository: Administrér repository - permission_manage_members: Administrér medlemmer - mail_subject_reminder: "%{count} sag(er) har deadline i de kommende dage (%{days})" - permission_add_issue_notes: Tilføj noter - permission_edit_messages: Redigér beskeder - permission_view_issue_watchers: Se liste over overvågere - permission_commit_access: Commit adgang - setting_mail_handler_api_key: API nøgle - label_example: Eksempel - permission_rename_wiki_pages: Omdøb wiki sider - text_custom_field_possible_values_info: 'En linje for hver værdi' - permission_view_wiki_pages: Se wiki - permission_edit_project: Redigér projekt - permission_save_queries: Gem forespørgsler - label_copied: kopieret - text_repository_usernames_mapping: "Vælg eller opdatér de Redmine brugere der svarer til de enkelte brugere fundet i repository loggen.\nBrugere med samme brugernavn eller email adresse i både Redmine og det valgte repository bliver automatisk koblet sammen." - permission_edit_time_entries: Redigér tidsregistreringer - general_csv_decimal_separator: ',' - permission_edit_own_time_entries: Redigér egne tidsregistreringer - setting_repository_log_display_limit: Højeste antal revisioner vist i fil-log - setting_file_max_size_displayed: Maksimale størrelse på tekstfiler vist inline - field_watcher: Overvåger - setting_openid: Tillad OpenID login og registrering - field_identity_url: OpenID URL - label_login_with_open_id_option: eller login med OpenID - setting_per_page_options: Enheder per side muligheder - mail_body_reminder: "%{count} sage(er) som er tildelt dig har deadline indenfor de næste %{days} dage:" - field_content: Indhold - label_descending: Aftagende - label_sort: Sortér - label_ascending: Tiltagende - label_date_from_to: Fra %{start} til %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Denne side har %{descendants} underside(r) og afledte. Hvad vil du gøre? - text_wiki_page_reassign_children: Flyt undersider til denne side - text_wiki_page_nullify_children: Behold undersider som rod-sider - text_wiki_page_destroy_children: Slet undersider ogalle deres afledte sider. - setting_password_min_length: Mindste længde på kodeord - field_group_by: Gruppér resultater efter - mail_subject_wiki_content_updated: "'%{id}' wikisiden er blevet opdateret" - label_wiki_content_added: Wiki side tilføjet - mail_subject_wiki_content_added: "'%{id}' wikisiden er blevet tilføjet" - mail_body_wiki_content_added: The '%{id}' wikiside er blevet tilføjet af %{author}. - label_wiki_content_updated: Wikiside opdateret - mail_body_wiki_content_updated: Wikisiden '%{id}' er blevet opdateret af %{author}. - permission_add_project: Opret projekt - setting_new_project_user_role_id: Denne rolle gives til en bruger, som ikke er administrator, og som opretter et projekt - label_view_all_revisions: Se alle revisioner - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Der er ingen sagshåndtering for dette projekt. Kontrollér venligst projektindstillingerne. - error_no_default_issue_status: Der er ikke defineret en standardstatus. Kontrollér venligst indstillingerne (gå til "Administration -> Sagsstatusser"). - text_journal_changed: "%{label} ændret fra %{old} til %{new}" - text_journal_set_to: "%{label} sat til %{value}" - text_journal_deleted: "%{label} slettet (%{old})" - label_group_plural: Grupper - label_group: Grupper - label_group_new: Ny gruppe - label_time_entry_plural: Anvendt tid - text_journal_added: "%{label} %{value} tilføjet" - field_active: Aktiv - enumeration_system_activity: System Aktivitet - permission_delete_issue_watchers: Slet overvågere - version_status_closed: lukket - version_status_locked: låst - version_status_open: åben - error_can_not_reopen_issue_on_closed_version: En sag tildelt en lukket version kan ikke genåbnes - label_user_anonymous: Anonym - button_move_and_follow: Flyt og overvåg - setting_default_projects_modules: Standard moduler, aktiveret for nye projekter - setting_gravatar_default: Standard Gravatar billede - field_sharing: Delning - label_version_sharing_hierarchy: Med projekthierarki - label_version_sharing_system: Med alle projekter - label_version_sharing_descendants: Med underprojekter - label_version_sharing_tree: Med projekttræ - label_version_sharing_none: Ikke delt - error_can_not_archive_project: Dette projekt kan ikke arkiveres - button_duplicate: Duplikér - button_copy_and_follow: Kopiér og overvåg - label_copy_source: Kilde - setting_issue_done_ratio: Beregn sagsløsning ratio - setting_issue_done_ratio_issue_status: Benyt sagsstatus - error_issue_done_ratios_not_updated: Sagsløsnings ratio, ikke opdateret. - error_workflow_copy_target: Vælg venligst måltracker og rolle(r) - setting_issue_done_ratio_issue_field: Benyt sagsfelt - label_copy_same_as_target: Samme som mål - label_copy_target: Mål - notice_issue_done_ratios_updated: Sagsløsningsratio opdateret. - error_workflow_copy_source: Vælg venligst en kildetracker eller rolle - label_update_issue_done_ratios: Opdater sagsløsningsratio - setting_start_of_week: Start kalendre på - permission_view_issues: Vis sager - label_display_used_statuses_only: Vis kun statusser der er benyttet af denne tracker - label_revision_id: Revision %{value} - label_api_access_key: API nøgle - label_api_access_key_created_on: API nøgle genereret %{value} siden - label_feeds_access_key: RSS nøgle - notice_api_access_key_reseted: Din API nøgle er nulstillet. - setting_rest_api_enabled: Aktiver REST web service - label_missing_api_access_key: Mangler en API nøgle - label_missing_feeds_access_key: Mangler en RSS nøgle - button_show: Vis - text_line_separated: Flere væredier tilladt (en linje for hver værdi). - setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer - permission_add_subprojects: Lav underprojekter - label_subproject_new: Nyt underprojekt - text_own_membership_delete_confirmation: |- - Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter. - Er du sikker på du ønsker at fortsætte? - label_close_versions: Luk færdige versioner - label_board_sticky: Klistret - label_board_locked: Låst - permission_export_wiki_pages: Eksporter wiki sider - setting_cache_formatted_text: Cache formatteret tekst - permission_manage_project_activities: Administrer projektaktiviteter - error_unable_delete_issue_status: Det var ikke muligt at slette sagsstatus - label_profile: Profil - permission_manage_subtasks: Administrer underopgaver - field_parent_issue: Hovedopgave - label_subtask_plural: Underopgaver - label_project_copy_notifications: Send email notifikationer, mens projektet kopieres - error_can_not_delete_custom_field: Kan ikke slette brugerdefineret felt - error_unable_to_connect: Kan ikke forbinde (%{value}) - error_can_not_remove_role: Denne rolle er i brug og kan ikke slettes. - error_can_not_delete_tracker: Denne type indeholder sager og kan ikke slettes. - field_principal: Principal - label_my_page_block: blok - notice_failed_to_save_members: "Fejl under lagring af medlem(mer): %{errors}." - text_zoom_out: Zoom ud - text_zoom_in: Zoom ind - notice_unable_delete_time_entry: Kan ikke slette tidsregistrering. - label_overall_spent_time: Overordnet forbrug af tid - field_time_entries: Log tid - project_module_gantt: Gantt - project_module_calendar: Kalender - button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: %{page_title}" - field_text: Tekstfelt - label_user_mail_option_only_owner: Kun for ting jeg er ejer af - setting_default_notification_option: Standardpåmindelsesmulighed - label_user_mail_option_only_my_events: Kun for ting jeg overvåger eller er involveret i - label_user_mail_option_only_assigned: Kun for ting jeg er tildelt - label_user_mail_option_none: Ingen hændelser - field_member_of_group: Medlem af gruppe - field_assigned_to_role: Medlem af rolle - notice_not_authorized_archived_project: Projektet du prøver at tilgå, er blevet arkiveret. - 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 - notice_gantt_chart_truncated: Kortet er blevet afkortet, fordi det overstiger det maksimale antal elementer, der kan vises (%{max}) - setting_gantt_items_limit: Maksimalt antal af elementer der kan vises på gantt kortet - - 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: Kodning af Commit beskeder - 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}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 sag - one: 1 sag - other: "%{count} sager" - 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: alle - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Med underprojekter - label_cross_project_tree: Med projekttræ - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alle projekter - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a571b427a436ebec45f1319212126a1f8161df94.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a571b427a436ebec45f1319212126a1f8161df94.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a57b60c6fc705f8d479ec2528b8b4f74bf1f817f.svn-base --- a/.svn/pristine/a5/a57b60c6fc705f8d479ec2528b8b4f74bf1f817f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 HookTest < ActionController::IntegrationTest - fixtures :users, :roles, :projects, :members, :member_roles - - # Hooks that are 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 - - class SidebarContent < Redmine::Hook::ViewListener - def view_layouts_base_sidebar(context) - content_tag('p', 'Sidebar hook') - end - end - - class ContentForInsideHook < Redmine::Hook::ViewListener - render_on :view_welcome_index_left, :inline => <<-VIEW -<% content_for :header_tags do %> - <%= javascript_include_tag 'test_plugin.js', :plugin => 'test_plugin' %> - <%= stylesheet_link_tag 'test_plugin.css', :plugin => 'test_plugin' %> -<% end %> - -

    ContentForInsideHook content

    -VIEW - end - - def setup - Redmine::Hook.clear_listeners - end - - def teardown - Redmine::Hook.clear_listeners - end - - def test_html_head_hook_response - Redmine::Hook.add_listener(ProjectBasedTemplate) - - get '/projects/ecookbook' - assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, - :parent => {:tag => 'head'} - end - - def test_empty_sidebar_should_be_hidden - get '/' - assert_select 'div#main.nosidebar' - end - - def test_sidebar_with_hook_content_should_not_be_hidden - Redmine::Hook.add_listener(SidebarContent) - - get '/' - assert_select 'div#sidebar p', :text => 'Sidebar hook' - assert_select 'div#main' - assert_select 'div#main.nosidebar', 0 - end - - def test_hook_with_content_for_should_append_content - Redmine::Hook.add_listener(ContentForInsideHook) - - get '/' - assert_response :success - assert_select 'p', :text => 'ContentForInsideHook content' - assert_select 'head' do - assert_select 'script[src=/plugin_assets/test_plugin/javascripts/test_plugin.js]' - assert_select 'link[href=/plugin_assets/test_plugin/stylesheets/test_plugin.css]' - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5815d0cddf1fb8e38d76e9186765b1d40c42e1c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5815d0cddf1fb8e38d76e9186765b1d40c42e1c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,191 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + test "Mapper#initialize should define a root MenuNode if menu is not present in items" do + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) + node = menu_mapper.menu_items + assert_not_nil node + assert_equal :root, node.name + end + + test "Mapper#initialize should use existing MenuNode if present" do + node = "foo" # just an arbitrary reference + menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {:test_menu => node}) + assert_equal node, menu_mapper.menu_items + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a58853dd4ead2341935313f54ac169192e2f2345.svn-base --- a/.svn/pristine/a5/a58853dd4ead2341935313f54ac169192e2f2345.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,231 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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.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 > 5} - @project.reload - assert_equal 5, @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 - end - - def test_entries_for_invalid_path_should_return_nil - entries = @repository.entries('invalid_path') - assert_nil entries - 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.filechanges.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5a34b2cada8528aefb674b60eab16486ab7bca6.svn-base --- a/.svn/pristine/a5/a5a34b2cada8528aefb674b60eab16486ab7bca6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class FilesControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :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 - - def test_index - get :index, :project_id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:containers) - - # file attached to the project - assert_tag :a, :content => 'project_file.zip', - :attributes => { :href => '/attachments/download/8/project_file.zip' } - - # file attached to a project's version - assert_tag :a, :content => 'version_file.zip', - :attributes => { :href => '/attachments/download/9/version_file.zip' } - end - - def test_new - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - - assert_tag 'select', :attributes => {:name => 'version_id'} - end - - def test_new_without_versions - Version.delete_all - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - - assert_no_tag 'select', :attributes => {:name => 'version_id'} - end - - def test_create_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - ActionMailer::Base.deliveries.clear - - with_settings :notified_events => %w(file_added) do - assert_difference 'Attachment.count' do - post :create, :project_id => 1, :version_id => '', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - assert_response :redirect - end - end - assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Project.find(1), a.container - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal "[eCookbook] New file", mail.subject - assert_mail_body_match 'testfile.txt', mail - end - - def test_create_version_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - - assert_difference 'Attachment.count' do - post :create, :project_id => 1, :version_id => '2', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - assert_response :redirect - end - assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Version.find(2), a.container - end - -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5c1742116d103566daddf0042d6bf11b7196800.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5c1742116d103566daddf0042d6bf11b7196800.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,601 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5ccd83a86dbec61cffcefbeb376115743cf6510.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a5/a5ccd83a86dbec61cffcefbeb376115743cf6510.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,89 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 HookTest < ActionController::IntegrationTest + fixtures :users, :roles, :projects, :members, :member_roles + + # Hooks that are 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 + + class SidebarContent < Redmine::Hook::ViewListener + def view_layouts_base_sidebar(context) + content_tag('p', 'Sidebar hook') + end + end + + class ContentForInsideHook < Redmine::Hook::ViewListener + render_on :view_welcome_index_left, :inline => <<-VIEW +<% content_for :header_tags do %> + <%= javascript_include_tag 'test_plugin.js', :plugin => 'test_plugin' %> + <%= stylesheet_link_tag 'test_plugin.css', :plugin => 'test_plugin' %> +<% end %> + +

    ContentForInsideHook content

    +VIEW + end + + def setup + Redmine::Hook.clear_listeners + end + + def teardown + Redmine::Hook.clear_listeners + end + + def test_html_head_hook_response + Redmine::Hook.add_listener(ProjectBasedTemplate) + + get '/projects/ecookbook' + assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, + :parent => {:tag => 'head'} + end + + def test_empty_sidebar_should_be_hidden + get '/' + assert_select 'div#main.nosidebar' + end + + def test_sidebar_with_hook_content_should_not_be_hidden + Redmine::Hook.add_listener(SidebarContent) + + get '/' + assert_select 'div#sidebar p', :text => 'Sidebar hook' + assert_select 'div#main' + assert_select 'div#main.nosidebar', 0 + end + + def test_hook_with_content_for_should_append_content + Redmine::Hook.add_listener(ContentForInsideHook) + + get '/' + assert_response :success + assert_select 'p', :text => 'ContentForInsideHook content' + assert_select 'head' do + assert_select 'script[src=/plugin_assets/test_plugin/javascripts/test_plugin.js]' + assert_select 'link[href=/plugin_assets/test_plugin/stylesheets/test_plugin.css]' + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5de9cb99d68c8846c05c740505204c82d676643.svn-base --- a/.svn/pristine/a5/a5de9cb99d68c8846c05c740505204c82d676643.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -module ObjectHelpers - def User.generate!(attributes={}) - @generated_user_login ||= 'user0' - @generated_user_login.succ! - user = User.new(attributes) - user.login = @generated_user_login 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 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 if project.name.blank? - project.identifier = @generated_project_identifier if project.identifier.blank? - yield project if block_given? - project.save! - 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? - 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 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 if version.name.blank? - yield version if block_given? - version.save! - version - 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? - 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 if board.name.blank? - board.description = @generated_board_name 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 if attachment.filename.blank? - attachment.save! - attachment - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a5/a5eae59bbce6cca98bb354eb803d70ece1bc50d0.svn-base --- a/.svn/pristine/a5/a5eae59bbce6cca98bb354eb803d70ece1bc50d0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,798 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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' - -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 = Iconv.conv('UTF-16BE', 'UTF-8', txt) - hextxt = "" - rescue - txt - end || '' - super(txt) - end - - def textstring(s) - # Format a text string - if s =~ /^\{\{([<>]?)toc\}\}<\/p>/i, '') - html - 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 = Iconv.conv('UTF-16BE', 'UTF-8', 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, col_id_width, 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 + col_id_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) - 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 - 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 - col_width = [] - unless query.inline_columns.empty? - col_width = calc_col_width(issues, query, table_width - col_id_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 = w * (page_width - right_margin - 10 - col_id_width) / 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, col_id_width, 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 + col_id_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, col_id_width, 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) - 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) - def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, - id_width, col_widths) - col_x = top_x + id_width - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a630a39c4ba7dc7a2645fe3c5b9313ac357232d8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a630a39c4ba7dc7a2645fe3c5b9313ac357232d8.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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! + after_create :send_notification + + 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 + + def send_notification + if Setting.notified_events.include?('message_posted') + Mailer.message_posted(self).deliver + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a649e6e13fa94c6da3001118983e1f589fe01ce6.svn-base --- a/.svn/pristine/a6/a649e6e13fa94c6da3001118983e1f589fe01ce6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,328 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 WorkflowsControllerTest < ActionController::TestCase - fixtures :roles, :trackers, :workflows, :users, :issue_statuses - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - - count = WorkflowTransition.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 - WorkflowTransition.delete_all - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) - WorkflowTransition.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 - WorkflowTransition.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, 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 - 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, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count - - 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.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first - assert w.author - assert ! w.assignee - 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.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first - assert w.author - assert w.assignee - end - - def test_clear_workflow - assert WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0 - - post :edit, :role_id => 2, :tracker_id => 1 - assert_equal 0, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - end - - def test_get_permissions - get :permissions - - assert_response :success - assert_template 'permissions' - assert_not_nil assigns(:roles) - assert_not_nil assigns(:trackers) - end - - def test_get_permissions_with_role_and_tracker - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - get :permissions, :role_id => 1, :tracker_id => 2 - assert_response :success - assert_template 'permissions' - - assert_select 'input[name=role_id][value=1]' - assert_select 'input[name=tracker_id][value=2]' - - # Required field - assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]' - end - - # Read-only field - assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]' - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - - # Other field - assert_select 'select[name=?]', 'permissions[due_date][3]' do - assert_select 'option[value=]' - assert_select 'option[value=][selected=selected]', 0 - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=readonly][selected=selected]', 0 - assert_select 'option[value=required]', :text => 'Required' - assert_select 'option[value=required][selected=selected]', 0 - end - end - - def test_get_permissions_with_required_custom_field_should_not_show_required_option - cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) - - get :permissions, :role_id => 1, :tracker_id => 1 - assert_response :success - assert_template 'permissions' - - # Custom field that is always required - # The default option is "(Required)" - assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do - assert_select 'option[value=]' - assert_select 'option[value=readonly]', :text => 'Read-only' - assert_select 'option[value=required]', 0 - end - end - - def test_get_permissions_with_role_and_tracker_and_all_statuses - WorkflowTransition.delete_all - - get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0' - assert_response :success - assert_equal IssueStatus.sorted.all, assigns(:statuses) - end - - def test_post_permissions - WorkflowPermission.delete_all - - post :permissions, :role_id => 1, :tracker_id => 2, :permissions => { - 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''}, - 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''}, - 'due_date' => {'1' => '', '2' => '', '3' => ''}, - } - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 3, workflows.size - workflows.each do |workflow| - assert_equal 1, workflow.role_id - assert_equal 2, workflow.tracker_id - end - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'} - assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'} - assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'} - end - - def test_post_permissions_should_clear_permissions - WorkflowPermission.delete_all - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') - WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') - wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') - - post :permissions, :role_id => 1, :tracker_id => 2 - assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' - - workflows = WorkflowPermission.all - assert_equal 2, workflows.size - assert wf1.reload - assert wf2.reload - end - - def test_get_copy - get :copy - assert_response :success - assert_template 'copy' - assert_select 'select[name=source_tracker_id]' do - assert_select 'option[value=1]', :text => 'Bug' - end - assert_select 'select[name=source_role_id]' do - assert_select 'option[value=2]', :text => 'Developer' - end - assert_select 'select[name=?]', 'target_tracker_ids[]' do - assert_select 'option[value=3]', :text => 'Support request' - end - assert_select 'select[name=?]', 'target_role_ids[]' do - assert_select 'option[value=1]', :text => 'Manager' - end - 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 - - 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. - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a65be5ff4e4d817d915292a1086456273d988086.svn-base --- a/.svn/pristine/a6/a65be5ff4e4d817d915292a1086456273d988086.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

    <%= link_to l(@enumeration.option_name), enumerations_path %> » <%=l(:label_enumeration_new)%>

    - -<%= labelled_form_for :enumeration, @enumeration, :url => enumerations_path do |f| %> - <%= f.hidden_field :type %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_create) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a677b960a442cc4fdd4c022f1d5c5fd8938b0275.svn-base --- a/.svn/pristine/a6/a677b960a442cc4fdd4c022f1d5c5fd8938b0275.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -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.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 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a68ae98331ab8e425c64f338d2d4aae23bc594f6.svn-base --- a/.svn/pristine/a6/a68ae98331ab8e425c64f338d2d4aae23bc594f6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 '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 - - def test_create - @request.session[:user_id] = 1 - assert_nil Project.find(3).wiki - - assert_difference 'Wiki.count' do - xhr :post, :edit, :id => 3, :wiki => { :start_page => 'Start page' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - wiki = Project.find(3).wiki - assert_not_nil wiki - assert_equal 'Start page', wiki.start_page - end - - def test_create_with_failure - @request.session[:user_id] = 1 - - assert_no_difference 'Wiki.count' do - xhr :post, :edit, :id => 3, :wiki => { :start_page => '' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - assert_include 'errorExplanation', response.body - assert_include 'Start page can't be blank', response.body - end - - def test_update - @request.session[:user_id] = 1 - - assert_no_difference 'Wiki.count' do - xhr :post, :edit, :id => 1, :wiki => { :start_page => 'Other start page' } - assert_response :success - assert_template 'edit' - assert_equal 'text/javascript', response.content_type - end - - wiki = Project.find(1).wiki - assert_equal 'Other start page', wiki.start_page - end - - def test_destroy - @request.session[:user_id] = 1 - post :destroy, :id => 1, :confirm => 1 - assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'ecookbook', :tab => 'wiki' - assert_nil Project.find(1).wiki - end - - def test_not_found - @request.session[:user_id] = 1 - post :destroy, :id => 999, :confirm => 1 - assert_response 404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a6ae8d4e019a56b1c1694cce112ce20ab4d1bb3c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a6/a6ae8d4e019a56b1c1694cce112ce20ab4d1bb3c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + test "GET /roles.xml 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 + + test "GET /roles.json 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 + + test "GET /roles/:id.xml 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a6be7752b3e1c7a6a368129ebcfd669e88823a53.svn-base --- a/.svn/pristine/a6/a6be7752b3e1c7a6a368129ebcfd669e88823a53.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a6/a6d8d977a7db837089d88e0eb10b4689db86305a.svn-base --- a/.svn/pristine/a6/a6d8d977a7db837089d88e0eb10b4689db86305a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,976 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::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})" - 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, :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 => /^(?!\d+$)[a-z0-9\-_]*$/, :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?} - 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 :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 - { :conditions => Project.allowed_to_condition(user, permission, *args) } - } - scope :like, lambda {|arg| - if arg.blank? - {} - else - pattern = "%#{arg.to_s.strip.downcase}%" - {:conditions => ["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') - self.trackers = Tracker.sorted.all - 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).find(:all, :limit => count, :order => "created_on DESC") - 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 - - 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 - super - 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.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]) - 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.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) - @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.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") - 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| - 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.find(:all, :include => [:user, :roles]).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 - [ - issues.minimum('start_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect(&:start_date) - ].flatten.compact.min - end - - # The latest due date of an issue or version - def due_date - [ - issues.maximum('due_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} - ].flatten.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_pourcent).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) } - - # 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.find(:first, :order => 'created_on DESC') - 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 - - - # Copies +project+ and returns the new instance. This will not save - # the copy - 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 - 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 - - # 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.find(:all, :order => 'root_id, lft').each do |issue| - new_issue = Issue.new - new_issue.copy_from(issue, :subtasks => false, :link => false) - new_issue.project = self - # 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 = ::Query.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. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + - self.time_entry_activities - else - return TimeEntryActivity.shared.active. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + - 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) - 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 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a70d1149c6a4c4bb1eb4ec1a50adfed6edf09844.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a70d1149c6a4c4bb1eb4ec1a50adfed6edf09844.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a71b998ee631f5527b99e3a0df2d62bbccac91fe.svn-base --- a/.svn/pristine/a7/a71b998ee631f5527b99e3a0df2d62bbccac91fe.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,269 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 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} - ], - :order => 'committed_on DESC' - ) - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a721c0d1c7040afbf54df15fe3439fbbed48e0c9.svn-base --- a/.svn/pristine/a7/a721c0d1c7040afbf54df15fe3439fbbed48e0c9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a757b81e7f3e085496f4684df875891a66fc60e7.svn-base --- a/.svn/pristine/a7/a757b81e7f3e085496f4684df875891a66fc60e7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,304 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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? - if User.current.logged? - redirect_to home_url - end - else - authenticate_user - end - rescue AuthSourceException => e - logger.error "An error occured when authenticating #{params[:username]}: #{e.message}" - render_error :message => e.message - end - - # Log out current user and redirect to welcome page - def logout - 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? - if params[:token] - @token = Token.find_token("recovery", params[:token].to_s) - if @token.nil? || @token.expired? - redirect_to home_url - return - end - @user = @token.user - unless @user && @user.active? - redirect_to home_url - return - end - 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 signin_path - return - end - end - render :template => "account/password_recovery" - return - else - if request.post? - user = User.find_by_mail(params[:mail].to_s) - # user not found or not active - unless user && user.active? - flash.now[:error] = l(:notice_account_unknown_email) - return - end - # user cannot change its password - unless user.change_password_allowed? - flash.now[:error] = l(:notice_can_t_change_password) - return - end - # create a new token for password recovery - token = Token.new(:user => user, :action => "recovery") - if token.save - Mailer.lost_password(token).deliver - flash[:notice] = l(:notice_account_lost_email_sent) - redirect_to signin_path - 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 => current_language.to_s) - else - user_params = params[:user] || {} - @user = User.new - @user.safe_attributes = user_params - @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 my_account_path - end - else - @user.login = params[:user][:login] - unless user_params[:identity_url].present? && user_params[:password].blank? && user_params[:password_confirmation].blank? - @user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation] - end - - 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].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? - user.activate - if user.save - token.destroy - flash[:notice] = l(:notice_account_activated) - end - redirect_to signin_path - end - - private - - 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) - 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? - - # 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) - logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}" - # 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 my_page_path - end - - def set_autologin_cookie(user) - token = Token.create(:user => user, :action => '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[autologin_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.register(token).deliver - flash[:notice] = l(:notice_account_register_done) - redirect_to signin_path - 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 my_account_path - 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.account_activation_request(user).deliver - account_pending - else - yield if block_given? - end - end - - def account_pending - flash[:notice] = l(:notice_account_pending) - redirect_to signin_path - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a781bd953d0066a0cb212d6ea0e21fdfc9eb41c7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a7/a781bd953d0066a0cb212d6ea0e21fdfc9eb41c7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,846 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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. + joins(:custom_values). + where(: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. + joins(:custom_values). + where(: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.where(: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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a7836f4a070a9a0d96511cf7847d79f9fcfd69e4.svn-base --- a/.svn/pristine/a7/a7836f4a070a9a0d96511cf7847d79f9fcfd69e4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a78648685c99f33273975ef82cbc56723c1630db.svn-base --- a/.svn/pristine/a7/a78648685c99f33273975ef82cbc56723c1630db.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ ---- -custom_fields_trackers_001: - custom_field_id: 1 - tracker_id: 1 -custom_fields_trackers_002: - custom_field_id: 2 - tracker_id: 1 -custom_fields_trackers_003: - custom_field_id: 2 - tracker_id: 3 -custom_fields_trackers_004: - custom_field_id: 6 - tracker_id: 1 -custom_fields_trackers_005: - custom_field_id: 6 - tracker_id: 2 -custom_fields_trackers_006: - custom_field_id: 6 - tracker_id: 3 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a7add1443588b3af2ef99c91219368b928dd34d0.svn-base --- a/.svn/pristine/a7/a7add1443588b3af2ef99c91219368b928dd34d0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 TimeReport - attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods - - def initialize(project, issue, criteria, columns, from, to) - @project = project - @issue = issue - - @criteria = criteria || [] - @criteria = @criteria.select{|criteria| available_criteria.has_key? criteria} - @criteria.uniq! - @criteria = @criteria[0,3] - - @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month' - @from = from - @to = to - - run - end - - def available_criteria - @available_criteria || load_available_criteria - end - - private - - 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| - h = {'hours' => hours} - (@criteria + time_columns).each_with_index do |name, i| - h[name] = hash[i] - end - @hours << h - end - - @hours.each do |row| - case @columns - when 'year' - row['year'] = row['tyear'] - when 'month' - row['month'] = "#{row['tyear']}-#{row['tmonth']}" - when 'week' - row['week'] = "#{row['tyear']}-#{row['tweek']}" - 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 - - if @to.nil? - max = @hours.collect {|row| row['spent_on']}.max - @to = max ? max.to_date : Date.today - end - - @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} - - @periods = [] - # Date#at_beginning_of_ not supported in Rails 1.2.x - date_from = @from.to_time - # 100 columns max - while date_from <= @to.to_time && @periods.length < 100 - case @columns - when 'year' - @periods << "#{date_from.year}" - date_from = (date_from + 1.year).at_beginning_of_year - when 'month' - @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}" - date_from = (date_from + 7.day).at_beginning_of_week - when 'day' - @periods << "#{date_from.to_date}" - date_from = date_from + 1.day - end - end - end - end - - def load_available_criteria - @available_criteria = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", - :klass => Project, - :label => :label_project}, - 'status' => {:sql => "#{Issue.table_name}.status_id", - :klass => IssueStatus, - :label => :field_status}, - 'version' => {:sql => "#{Issue.table_name}.fixed_version_id", - :klass => Version, - :label => :label_version}, - 'category' => {:sql => "#{Issue.table_name}.category_id", - :klass => IssueCategory, - :label => :field_category}, - 'member' => {:sql => "#{TimeEntry.table_name}.user_id", - :klass => User, - :label => :label_member}, - 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", - :klass => Tracker, - :label => :label_tracker}, - 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id", - :klass => TimeEntryActivity, - :label => :label_activity}, - 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id", - :klass => Issue, - :label => :label_issue} - } - - # 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)", - :format => cf.field_format, - :label => cf.name} - end - - @available_criteria - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a7/a7c6b64e7ff32670d95a0267494d6a79be487e89.svn-base --- a/.svn/pristine/a7/a7c6b64e7ff32670d95a0267494d6a79be487e89.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1109 +0,0 @@ -pt-BR: - 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 h" - time: "%H:%M h" - short: "%d/%m, %H:%M h" - long: "%A, %d de %B de %Y, %H:%M h" - 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_hours: - one: "1 hora" - other: "%{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: "quase 1 ano" - other: "quase %{count} anos" - - # 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: 3 - 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: "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" - 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ínimo: %{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:" - - - 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_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_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_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_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 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 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 - 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 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). - 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 - 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 sub-tarefas - 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}" - 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: Tarefa %{id} criada. - label_between: entre - setting_issue_group_assignment: Permitir atribuições de tarefas a grupos - label_diff: diff - text_git_repository_note: "Repositório esta vazio e é local (ex: /gitrepo, c:\\gitrepo)" - - description_query_sort_criteria_direction: Direção da ordenação - 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: Atributo de ordenação - description_wiki_subpages_reassign: Escolha uma nova página pai - description_selected_columns: Colunas selecionadas - - label_parent_revision: Pais - label_child_revision: Filhos - error_scm_annotate_big_text_file: A entrada não pode ser anotada, pois excede o tamanho máximo do arquivo de texto. - setting_default_issue_start_date_to_creation_date: Usar data corrente como data inicial para novas tarefas - button_edit_section: Editar esta seção - setting_repositories_encodings: Encoding dos repositórios e anexos - description_all_columns: Todas as colunas - button_export: Exportar - label_export_options: "Opções de exportação %{export_format}" - error_attachment_too_big: Este arquivo não pode ser enviado porque excede o tamanho máximo permitido (%{max_size}) - notice_failed_to_save_time_entries: "Falha ao salvar %{count} de %{total} horas trabalhadas: %{ids}." - label_x_issues: - zero: 0 tarefa - one: 1 tarefa - other: "%{count} tarefas" - label_repository_new: Novo repositório - field_repository_is_default: Repositório principal - label_copy_attachments: Copiar anexos - label_item_position: "%{position}/%{count}" - label_completed_versions: Versões completadas - text_project_identifier_info: Somente letras minúsculas (az), números, traços e sublinhados são permitidos.
    Uma vez salvo, o identificador não pode ser alterado. - field_multiple: Multiplos valores - setting_commit_cross_project_ref: Permitir que tarefas de todos os outros projetos sejam refenciadas e resolvidas - text_issue_conflict_resolution_add_notes: Adicione minhas anotações e descartar minhas outras mudanças - text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações de qualquer maneira (notas anteriores serão mantidos, mas algumas mudanças podem ser substituídos) - notice_issue_update_conflict: A tarefa foi atualizada por um outro usuário, enquanto você estava editando. - text_issue_conflict_resolution_cancel: Descartar todas as minhas mudanças e re-exibir %{link} - permission_manage_related_issues: Gerenciar tarefas relacionadas - field_auth_source_ldap_filter: Filtro LDAP - label_search_for_watchers: Procurar por outros observadores para adiconar - notice_account_deleted: Sua conta foi excluída permanentemente. - setting_unsubscribe: Permitir aos usuários excluir sua conta própria - button_delete_my_account: Excluir minha conta - text_account_destroy_confirmation: |- - Tem certeza de que quer continuar? - Sua conta será excluída permanentemente, sem qualquer forma de reativá-lo. - error_session_expired: A sua sessão expirou. Por favor, faça login novamente. - text_session_expiration_settings: "Aviso: a alteração dessas configurações pode expirar as sessões atuais, incluindo a sua." - setting_session_lifetime: duração máxima da sessão - setting_session_timeout: tempo limite de inatividade da sessão - label_session_expiration: "Expiração da sessão" - permission_close_project: Fechar / reabrir o projeto - label_show_closed_projects: Visualização de projetos fechados - button_close: Fechar - button_reopen: Reabrir - project_status_active: ativo - project_status_closed: fechado - project_status_archived: arquivado - text_project_closed: Este projeto é fechado e somente leitura. - 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_size: Tamanho das miniaturas (em pixels) - label_status_transitions: Estados das transições - label_fields_permissions: Permissões de campos - label_readonly: somente leitura - label_required: Obrigatório - text_repository_identifier_info: Somente letras minúsculas (az), números, traços e sublinhados são permitidos
    Uma vez salvo, o identificador não pode ser alterado. - field_board_parent: Fórum Pai - label_attribute_of_project: "Projeto %{name}" - label_attribute_of_author: "autor %{name}" - label_attribute_of_assigned_to: "atribuído %{name}" - label_attribute_of_fixed_version: "versão alvo %{name}" - label_copy_subtasks: Copiar sub-tarefas - label_copied_to: copiada - label_copied_from: copiado - label_any_issues_in_project: quaisquer problemas em projeto - 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: 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 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a8/a81b13dc0401f67f1e9b5f7bed01d01362dcb1f6.svn-base --- a/.svn/pristine/a8/a81b13dc0401f67f1e9b5f7bed01d01362dcb1f6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1120 +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: 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 hour" - other: "%{count} hours" - 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: 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_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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a8/a8b00183ca19aefa144aaaa16c35a73416e33f9f.svn-base --- a/.svn/pristine/a8/a8b00183ca19aefa144aaaa16c35a73416e33f9f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -<% if @project.boards.any? %> - - - - - - - - -<% Board.board_tree(@project.boards) do |board, level| - next if board.new_record? %> - - - - - - -<% end %> - -
    <%= l(:label_board) %><%= l(:field_description) %>
    <%= link_to board.name, project_board_path(@project, board) %><%=h board.description %> - <% if authorize_for("boards", "edit") %> - <%= reorder_links('board', {:controller => 'boards', :action => 'update', :project_id => @project, :id => board}, :put) %> - <% end %> - - <% if User.current.allowed_to?(:manage_boards, @project) %> - <%= link_to l(:button_edit), edit_project_board_path(@project, board), :class => 'icon icon-edit' %> - <%= delete_link project_board_path(@project, board) %> - <% end %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<% if User.current.allowed_to?(:manage_boards, @project) %> -

    <%= link_to l(:label_board_new), new_project_board_path(@project), :class => 'icon icon-add' %>

    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a8/a8e1823f8c3ae4f3c12b337afb4603e98ef2cbff.svn-base --- a/.svn/pristine/a8/a8e1823f8c3ae4f3c12b337afb4603e98ef2cbff.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -<%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> -<%= issue_url %> - -<%= render_email_issue_attributes(issue) %> ----------------------------------------- -<%= issue.description %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a8/a8fc14f9eb6b10ebe9da4200ebea010b2daee352.svn-base --- a/.svn/pristine/a8/a8fc14f9eb6b10ebe9da4200ebea010b2daee352.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,450 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'cgi' - -if RUBY_VERSION < '1.9' - require 'iconv' -end - -module Redmine - module Scm - module Adapters - class CommandFailed < StandardError #:nodoc: - end - - class AbstractAdapter #:nodoc: - - # raised if scm command exited with error, e.g. unknown revision. - class ScmCommandAborted < CommandFailed; end - - class << self - def client_command - "" - end - - def shell_quote_command - if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java' - client_command - else - shell_quote(client_command) - end - end - - # Returns the version of the scm client - # Eg: [1, 5, 0] or [] if unknown - def client_version - [] - end - - # Returns the version string of the scm client - # Eg: '1.5.0' or 'Unknown version' if unknown - def client_version_string - v = client_version || 'Unknown version' - v.is_a?(Array) ? v.join('.') : v.to_s - end - - # Returns true if the current client version is above - # or equals the given one - # If option is :unknown is set to true, it will return - # true if the client version is unknown - def client_version_above?(v, options={}) - ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) - end - - def client_available - true - end - - def shell_quote(str) - if Redmine::Platform.mswin? - '"' + str.gsub(/"/, '\\"') + '"' - else - "'" + str.gsub(/'/, "'\"'\"'") + "'" - end - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = url - @login = login if login && !login.empty? - @password = (password || "") if @login - @root_url = root_url.blank? ? retrieve_root_url : root_url - end - - def adapter_name - 'Abstract' - end - - def supports_cat? - true - end - - def supports_annotate? - respond_to?('annotate') - end - - def root_url - @root_url - end - - def url - @url - end - - def path_encoding - nil - end - - # get info about the svn repository - def info - return nil - end - - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - 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) - es ? es.detect {|e| e.name == search_name} : nil - end - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - def entries(path=nil, identifier=nil, options={}) - return nil - end - - def branches - return nil - end - - def tags - return nil - end - - def default_branch - return nil - end - - def properties(path, identifier=nil) - return nil - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - return nil - end - - def diff(path, identifier_from, identifier_to=nil) - return nil - end - - def cat(path, identifier=nil) - return nil - end - - def with_leading_slash(path) - path ||= '' - (path[0,1]!="/") ? "/#{path}" : path - end - - def with_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path : "#{path}/" - end - - def without_leading_slash(path) - path ||= '' - path.gsub(%r{^/+}, '') - end - - def without_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path[0..-2] : path - end - - def shell_quote(str) - self.class.shell_quote(str) - end - - private - def retrieve_root_url - info = self.info - info ? info.root_url : nil - end - - def target(path, sq=true) - path ||= '' - base = path.match(/^\//) ? root_url : url - str = "#{base}/#{path}".gsub(/[?<>\*]/, '') - if sq - str = shell_quote(str) - end - str - end - - def logger - self.class.logger - end - - def shellout(cmd, options = {}, &block) - self.class.shellout(cmd, options, &block) - end - - def self.logger - 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)}" - # Capture stderr in a log file - if stderr_log_file - cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}" - end - end - begin - mode = "r+" - IO.popen(cmd, mode) do |io| - io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding) - io.close_write unless options[:write_stdin] - block.call(io) if block_given? - end - ## If scm command does not exist, - ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException - ## in production environment. - # rescue Errno::ENOENT => e - rescue Exception => e - msg = strip_credential(e.message) - # The command failed, log it and re-raise - logmsg = "SCM command failed, " - logmsg += "make sure that your SCM command (e.g. svn) is " - logmsg += "in PATH (#{ENV['PATH']})\n" - logmsg += "You can configure your scm commands in config/configuration.yml.\n" - logmsg += "#{strip_credential(cmd)}\n" - logmsg += "with: #{msg}" - logger.error(logmsg) - raise CommandFailed.new(msg) - end - end - - # Hides username/password in a given command - def self.strip_credential(cmd) - q = (Redmine::Platform.mswin? ? '"' : "'") - cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') - end - - def strip_credential(cmd) - self.class.strip_credential(cmd) - end - - def scm_iconv(to, from, str) - return nil if str.nil? - return str if to == from - if str.respond_to?(:force_encoding) - str.force_encoding(from) - begin - str.encode(to) - rescue Exception => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - else - begin - Iconv.conv(to, from, str) - rescue Iconv::Failure => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - end - end - - def parse_xml(xml) - if RUBY_PLATFORM == 'java' - xml = xml.sub(%r{<\?xml[^>]*\?>}, '') - end - ActiveSupport::XmlMini.parse(xml) - end - end - - class Entries < Array - def sort_by_name - dup.sort! {|x,y| - if x.kind == y.kind - x.name.to_s <=> y.name.to_s - else - x.kind <=> y.kind - end - } - end - - def revisions - revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) - end - end - - class Info - attr_accessor :root_url, :lastrev - def initialize(attributes={}) - self.root_url = attributes[:root_url] if attributes[:root_url] - self.lastrev = attributes[:lastrev] - end - end - - class Entry - attr_accessor :name, :path, :kind, :size, :lastrev, :changeset - - def initialize(attributes={}) - self.name = attributes[:name] if attributes[:name] - self.path = attributes[:path] if attributes[:path] - self.kind = attributes[:kind] if attributes[:kind] - self.size = attributes[:size].to_i if attributes[:size] - self.lastrev = attributes[:lastrev] - end - - def is_file? - 'file' == self.kind - end - - def is_dir? - 'dir' == self.kind - end - - def is_text? - Redmine::MimeType.is_type?('text', name) - end - - def author - if changeset - changeset.author.to_s - elsif lastrev - Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first) - end - end - end - - class Revisions < Array - def latest - sort {|x,y| - unless x.time.nil? or y.time.nil? - x.time <=> y.time - else - 0 - end - }.last - end - end - - class Revision - attr_accessor :scmid, :name, :author, :time, :message, - :paths, :revision, :branch, :identifier, - :parents - - def initialize(attributes={}) - self.identifier = attributes[:identifier] - self.scmid = attributes[:scmid] - self.name = attributes[:name] || self.identifier - self.author = attributes[:author] - self.time = attributes[:time] - self.message = attributes[:message] || "" - self.paths = attributes[:paths] - self.revision = attributes[:revision] - self.branch = attributes[:branch] - self.parents = attributes[:parents] - end - - # Returns the readable identifier. - def format_identifier - self.identifier.to_s - end - - def ==(other) - if other.nil? - false - elsif scmid.present? - scmid == other.scmid - elsif identifier.present? - identifier == other.identifier - elsif revision.present? - revision == other.revision - end - end - end - - class Annotate - attr_reader :lines, :revisions - - def initialize - @lines = [] - @revisions = [] - end - - def add_line(line, revision) - @lines << line - @revisions << revision - end - - def content - content = lines.join("\n") - end - - def empty? - lines.empty? - end - end - - class Branch < String - attr_accessor :revision, :scmid - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a9/a912667150d22dbe4bd798c3e7e202b84644fb93.svn-base --- a/.svn/pristine/a9/a912667150d22dbe4bd798c3e7e202b84644fb93.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -
    -<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %> -
    - -<%= render_timelog_breadcrumb %> - -

    <%= l(:label_spent_time) %>

    - -<%= form_tag({:controller => 'timelog', :action => 'report', - :project_id => @project, :issue_id => @issue}, - :method => :get, :id => 'query_form') do %> - <% @report.criteria.each do |criterion| %> - <%= hidden_field_tag 'criteria[]', criterion, :id => nil %> - <% end %> - <%= render :partial => 'timelog/date_range' %> - -

    : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], - [l(:label_month), 'month'], - [l(:label_week), 'week'], - [l(:label_day_plural).titleize, 'day']], @report.columns), - :onchange => "this.form.submit();" %> - - : <%= select_tag('criteria[]', options_for_select([[]] + (@report.available_criteria.keys - @report.criteria).collect{|k| [l_or_humanize(@report.available_criteria[k][:label]), k]}), - :onchange => "this.form.submit();", - :style => 'width: 200px', - :id => nil, - :disabled => (@report.criteria.length >= 3), :id => "criterias") %> - <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @report.columns}, :class => 'icon icon-reload' %>

    -<% end %> - -<% unless @report.criteria.empty? %> -
    -

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

    -
    - -<% unless @report.hours.empty? %> -
    - - - -<% @report.criteria.each do |criteria| %> - -<% end %> -<% columns_width = (40 / (@report.periods.length+1)).to_i %> -<% @report.periods.each do |period| %> - -<% end %> - - - - -<%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %> - - - <%= ('' * (@report.criteria.size - 1)).html_safe %> - <% total = 0 -%> - <% @report.periods.each do |period| -%> - <% sum = sum_hours(select_hours(@report.hours, @report.columns, period.to_s)); total += sum -%> - - <% end -%> - - - -
    <%= l_or_humanize(@report.available_criteria[criteria][:label]) %><%= period %><%= l(:label_total_time) %>
    <%= l(:label_total_time) %><%= html_hours("%.2f" % sum) if sum > 0 %><%= html_hours("%.2f" % total) if total > 0 %>
    -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'CSV', :url => params %> -<% end %> -<% end %> -<% end %> - -<% html_title l(:label_spent_time), l(:label_report) %> - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a9/a9200a514553078059a3a448009b624853a28547.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9200a514553078059a3a448009b624853a28547.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,176 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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_directory_with_override + @klass.register(:foo) do + directory '/path/to/foo' + end + assert_equal '/path/to/foo', @klass.find('foo').directory + end + + def test_directory_without_override + @klass.register(:foo) {} + assert_equal File.join(@klass.directory, 'foo'), @klass.find('foo').directory + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/a9/a9407e7175c72cbb7b95800538757dd1ce8a907c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9407e7175c72cbb7b95800538757dd1ce8a907c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + after_create :send_notification + + 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 + + def send_notification + if Setting.notified_events.include?('news_added') + Mailer.news_added(self).deliver + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a9/a9a37407adbe18522fea7abeb3093d6c557988f5.svn-base --- a/.svn/pristine/a9/a9a37407adbe18522fea7abeb3093d6c557988f5.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1104 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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:" - - gui_validation_error: 1 virhe - gui_validation_error_plural: "%{count} virhettä" - - 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_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 - 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_modification: "%{count} muutos" - label_modification_plural: "%{count} muutettu" - 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_manage_documents: Hallinnoi dokumentteja - permission_edit_wiki_pages: Muokkaa wiki sivuja - permission_add_issue_watchers: Lisää seuraajia - permission_view_gantt: Näytä gantt kaavio - permission_move_issues: Siirrä tapahtuma - permission_manage_issue_relations: Hallinoi tapahtuman suhteita - permission_delete_wiki_pages: Poista wiki sivuja - permission_manage_boards: Hallinnoi keskustelupalstaa - permission_delete_wiki_pages_attachments: Poista liitteitä - permission_view_wiki_edits: Näytä wiki historia - permission_add_messages: Jätä viesti - permission_view_messages: Näytä viestejä - permission_manage_files: Hallinnoi tiedostoja - permission_edit_issue_notes: Muokkaa muistiinpanoja - permission_manage_news: Hallinnoi uutisia - permission_view_calendar: Näytä kalenteri - permission_manage_members: Hallinnoi jäseniä - permission_edit_messages: Muokkaa viestejä - permission_delete_issues: Poista tapahtumia - permission_view_issue_watchers: Näytä seuraaja lista - permission_manage_repository: Hallinnoi tietovarastoa - permission_commit_access: Tee pääsyoikeus - permission_browse_repository: Selaa tietovarastoa - permission_view_documents: Näytä dokumentit - permission_edit_project: Muokkaa projektia - permission_add_issue_notes: Lisää muistiinpanoja - permission_save_queries: Tallenna hakuja - permission_view_wiki_pages: Näytä wiki - permission_rename_wiki_pages: Uudelleennimeä wiki sivuja - permission_edit_time_entries: Muokkaa aika lokeja - permission_edit_own_issue_notes: Muokkaa omia muistiinpanoja - setting_gravatar_enabled: Käytä Gravatar käyttäjä ikoneita - label_example: Esimerkki - text_repository_usernames_mapping: "Valitse päivittääksesi Redmine käyttäjä jokaiseen käyttäjään joka löytyy tietovaraston lokista.\nKäyttäjät joilla on sama Redmine ja tietovaraston käyttäjänimi tai sähköpostiosoite, yhdistetään automaattisesti." - permission_edit_own_messages: Muokkaa omia viestejä - permission_delete_own_messages: Poista omia viestejä - label_user_activity: "Käyttäjän %{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_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: 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 - 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}) - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/a9/a9d205d21aabcb1747a69fc0912d6918652b3b67.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/a9/a9d205d21aabcb1747a69fc0912d6918652b3b67.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,1270 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'forwardable' +require 'cgi' + +module ApplicationHelper + 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 + + # Return true if user is authorized for controller/action, otherwise false + def authorize_for(controller, action) + User.current.allowed_to?({:controller => controller, :action => action}, @project) + end + + # Display a link if user is authorized + # + # @param [String] name Anchor text (passed to link_to) + # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized + # @param [optional, Hash] html_options Options passed to link_to + # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to + def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) + link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) + end + + # Displays a link to user's account page if active + def link_to_user(user, options={}) + if user.is_a?(User) + name = h(user.name(options[:format])) + if user.active? || (User.current.admin? && user.logged?) + link_to name, user_path(user), :class => user.css_classes + else + name + end + else + h(user.to_s) + end + end + + # Displays a link to +issue+ with its subject. + # Examples: + # + # link_to_issue(issue) # => Defect #6: This is the subject + # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... + # link_to_issue(issue, :subject => false) # => Defect #6 + # link_to_issue(issue, :project => true) # => Foo - Defect #6 + # link_to_issue(issue, :subject => false, :tracker => false) # => #6 + # + def link_to_issue(issue, options={}) + title = nil + subject = nil + text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" + if options[:subject] == false + title = truncate(issue.subject, :length => 60) + else + subject = issue.subject + if options[:truncate] + subject = truncate(subject, :length => options[:truncate]) + end + end + only_path = options[:only_path].nil? ? true : options[:only_path] + s = link_to text, issue_path(issue, :only_path => only_path), :class => issue.css_classes, :title => title + s << h(": #{subject}") if subject + s = h("#{issue.project} - ") + s if options[:project] + s + end + + # Generates a link to an attachment. + # Options: + # * :text - Link text (default to attachment filename) + # * :download - Force download (default: false) + def link_to_attachment(attachment, options={}) + text = options.delete(:text) || attachment.filename + 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 + # Options: + # * :text - Link text (default to the formatted revision) + def link_to_revision(revision, repository, options={}) + if repository.is_a?(Project) + repository = repository.repository + end + text = options.delete(:text) || format_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision + link_to( + h(text), + {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, + :title => l(:label_revision_id, format_revision(revision)) + ) + end + + # Generates a link to a message + def link_to_message(message, options={}, html_options = nil) + link_to( + 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)), + html_options + ) + end + + # Generates a link to a project if active + # Examples: + # + # link_to_project(project) # => link to the specified project overview + # 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.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 + 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 + + def wiki_page_path(page, options={}) + url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) + end + + def thumbnail_tag(attachment) + link_to image_tag(thumbnail_path(attachment)), + named_attachment_path(attachment, attachment.filename), + :title => attachment.filename + end + + def toggle_link(name, id, options={}) + onclick = "$('##{id}').toggle(); " + onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") + onclick << "return false;" + link_to(name, "#", :onclick => onclick) + end + + def image_to_function(name, function, html_options = {}) + html_options.symbolize_keys! + tag(:input, html_options.merge({ + :type => "image", :src => image_path(name), + :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" + })) + end + + def format_activity_title(text) + h(truncate_single_line(text, :length => 100)) + end + + def format_activity_day(date) + date == User.current.today ? l(:label_today).titleize : format_date(date) + end + + def format_activity_description(text) + h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') + ).gsub(/[\r\n]+/, "
    ").html_safe + end + + def format_version_name(version) + if version.project == @project + h(version) + else + h("#{version.project} - #{version}") + end + end + + def due_date_distance_in_words(date) + if date + l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) + end + end + + # Renders a tree of projects as a nested set of unordered lists + # The given collection may be a subset of the whole project tree + # (eg. some intermediate nodes are private and can not be seen) + def render_project_nested_lists(projects) + s = '' + if projects.any? + ancestors = [] + original_project = @project + projects.sort_by(&:lft).each do |project| + # set the project environment to please macros. + @project = project + if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) + s << "
      \n" + else + ancestors.pop + s << "" + while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) + ancestors.pop + s << "
    \n" + end + end + classes = (ancestors.empty? ? 'root' : 'child') + s << "
  • " + s << h(block_given? ? yield(project) : project.name) + s << "
    \n" + ancestors << project + end + s << ("
  • \n" * ancestors.size) + @project = original_project + end + s.html_safe + end + + def render_page_hierarchy(pages, node=nil, options={}) + content = '' + if pages[node] + content << "
      \n" + pages[node].each do |page| + content << "
    • " + content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, + :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) + content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] + content << "
    • \n" + end + content << "
    \n" + end + content.html_safe + end + + # Renders flash messages + def render_flash_messages + s = '' + flash.each do |k,v| + s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") + end + s.html_safe + end + + # Renders tabs and their content + def render_tabs(tabs) + if tabs.any? + render :partial => 'common/tabs', :locals => {:tabs => tabs} + else + content_tag 'p', l(:label_no_data), :class => "nodata" + end + end + + # Renders the project quick-jump box + def render_project_jump_box + return unless User.current.logged? + projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq + if projects.any? + options = + ("" + + '').html_safe + + options << project_tree_options_for_select(projects, :selected => @project) do |p| + { :value => project_path(:id => p, :jump => current_menu_item) } + end + + select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') + end + end + + def project_tree_options_for_select(projects, options = {}) + s = '' + project_tree(projects) do |project, level| + name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe + tag_options = {:value => project.id} + if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) + tag_options[:selected] = 'selected' + else + tag_options[:selected] = nil + end + tag_options.merge!(yield(project)) if block_given? + s << content_tag('option', name_prefix + h(project), tag_options) + end + s.html_safe + end + + # Yields the given block for each project with its level in the tree + # + # Wrapper for Project#project_tree + def project_tree(projects, &block) + Project.project_tree(projects, &block) + end + + def principals_check_box_tags(name, principals) + s = '' + principals.each do |principal| + s << "\n" + end + s.html_safe + end + + # Returns a string for users/groups option tags + def principals_options_for_select(collection, selected=nil) + s = '' + if collection.include?(User.current) + s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) + end + groups = '' + collection.sort.each do |element| + selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected + (element.is_a?(Group) ? groups : s) << %() + end + unless groups.empty? + s << %(#{groups}) + end + s.html_safe + end + + # Options for the new membership projects combo-box + 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.to_a.include?(p)} + end + options + end + + def option_tag(name, text, value, selected=nil, options={}) + content_tag 'option', value, options.merge(:value => value, :selected => (value == selected)) + end + + # Truncates and returns the string as a single line + def truncate_single_line(string, *args) + truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') + end + + # Truncates at line break after 250 characters or options[:length] + def truncate_lines(string, options={}) + length = options[:length] || 250 + if string.to_s =~ /\A(.{#{length}}.*?)$/m + "#{$1}..." + else + string + end + end + + def anchor(text) + text.to_s.gsub(' ', '_') + end + + def html_hours(text) + text.gsub(%r{(\d+)\.(\d+)}, '\1.\2').html_safe + end + + def authoring(created, author, options={}) + l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe + end + + def time_tag(time) + text = distance_of_time_in_words(Time.now, time) + if @project + link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) + else + content_tag('abbr', text, :title => format_time(time)) + end + end + + def syntax_highlight_lines(name, content) + lines = [] + syntax_highlight(name, content).each_line { |line| lines << line } + lines + end + + def syntax_highlight(name, content) + Redmine::SyntaxHighlighting.highlight_by_filename(content, name) + end + + def to_path_param(path) + str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") + str.blank? ? nil : str + 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'}), + :method => method, :title => l(:label_sort_highest)) + + link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), + url.merge({"#{name}[move_to]" => 'higher'}), + :method => method, :title => l(:label_sort_higher)) + + link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), + url.merge({"#{name}[move_to]" => 'lower'}), + :method => method, :title => l(:label_sort_lower)) + + link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), + url.merge({"#{name}[move_to]" => 'lowest'}), + :method => method, :title => l(:label_sort_lowest)) + end + + def breadcrumb(*args) + elements = args.flatten + elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil + end + + def other_formats_links(&block) + concat('

    '.html_safe + l(:label_export_to)) + yield Redmine::Views::OtherFormatsBuilder.new(self) + concat('

    '.html_safe) + end + + def page_header_title + if @project.nil? || @project.new_record? + h(Setting.app_title) + else + b = [] + ancestors = (@project.root? ? [] : @project.ancestors.visible.all) + if ancestors.any? + root = ancestors.shift + b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') + if ancestors.size > 2 + b << "\xe2\x80\xa6" + ancestors = ancestors[-2, 2] + end + b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } + end + b << h(@project) + b.join(" \xc2\xbb ").html_safe + end + end + + # Returns a h2 tag and sets the html title with the given arguments + def title(*args) + strings = args.map do |arg| + if arg.is_a?(Array) && arg.size >= 2 + link_to(*arg) + else + h(arg.to_s) + end + end + html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s} + content_tag('h2', strings.join(' » ').html_safe) + end + + # Sets the html title + # Returns the html title when called without arguments + # Current project name and app_title and automatically appended + # Exemples: + # html_title 'Foo', 'Bar' + # html_title # => 'Foo - Bar - My Project - Redmine' + def html_title(*args) + if args.empty? + title = @html_title || [] + title << @project.name if @project + title << Setting.app_title unless Setting.app_title == title.last + title.reject(&:blank?).join(' - ') + else + @html_title ||= [] + @html_title += args + end + end + + # Returns the theme, controller name, and action as css classes for the + # HTML body. + def body_css_classes + css = [] + if theme = Redmine::Themes.theme(Setting.ui_theme) + css << 'theme-' + theme.name + end + + css << 'project-' + @project.identifier if @project && @project.identifier.present? + css << 'controller-' + controller_name + css << 'action-' + action_name + css.join(' ') + end + + def accesskey(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. + # 2 ways to call this method: + # * with a String: textilizable(text, options) + # * with an object and one of its attribute: textilizable(issue, :description, options) + def textilizable(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + case args.size + when 1 + obj = options[:object] + text = args.shift + when 2 + obj = args.shift + attr = args.shift + text = obj.send(attr).to_s + else + raise ArgumentError, 'invalid arguments to textilizable' + end + return '' if text.blank? + project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) + only_path = options.delete(:only_path) == false ? false : true + + text = text.dup + macros = catch_macros(text) + text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) + + @parsed_headings = [] + @heading_anchors = {} + @current_section = 0 if options[:edit_section_links] + + parse_sections(text, project, obj, attr, only_path, options) + text = parse_non_pre_blocks(text, obj, macros) do |text| + [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| + send method_name, text, project, obj, attr, only_path, options + end + end + parse_headings(text, project, obj, attr, only_path, options) + + if @parsed_headings.any? + replace_toc(text, @parsed_headings) + end + + text.html_safe + end + + def parse_non_pre_blocks(text, obj, macros) + s = StringScanner.new(text) + tags = [] + parsed = '' + while !s.eos? + s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) + text, full_tag, closing, tag = s[1], s[2], s[3], s[4] + if tags.empty? + yield text + inject_macros(text, obj, macros) if macros.any? + else + inject_macros(text, obj, macros, false) if macros.any? + end + parsed << text + if tag + if closing + if tags.last == tag.downcase + tags.pop + end + else + tags << tag.downcase + end + parsed << full_tag + end + end + # Close any non closing tags + while tag = tags.pop + parsed << "" + end + parsed + end + + def parse_inline_attachments(text, project, obj, attr, only_path, options) + # when using an image link, try to use an attachment, if possible + attachments = options[:attachments] || [] + attachments += obj.attachments if obj.respond_to?(:attachments) + if attachments.present? + text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| + filename, ext, alt, alttext = $1.downcase, $2, $3, $4 + # search for the picture in attachments + if found = Attachment.latest_attach(attachments, filename) + 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}\"" + end + "src=\"#{image_url}\"#{alt}" + else + m + end + end + end + end + + # Wiki links + # + # Examples: + # [[mypage]] + # [[mypage|mytext]] + # wiki links can refer other project wikis, using project name or identifier: + # [[project:]] -> wiki starting page + # [[project:|mytext]] + # [[project:mypage]] + # [[project:mypage|mytext]] + def parse_wiki_links(text, project, obj, attr, only_path, options) + text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| + link_project = project + esc, all, page, title = $1, $2, $3, $5 + if esc.nil? + if page =~ /^([^\:]+)\:(.*)$/ + 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 + # extract anchor + anchor = nil + if page =~ /^(.+?)\#(.+)$/ + page, anchor = $1, $2 + end + anchor = sanitize_anchor_name(anchor) if anchor.present? + # check if page exists + wiki_page = link_project.wiki.find_page(page) + url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page + "##{anchor}" + else + case options[:wiki_links] + when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') + when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export + else + wiki_page_id = page.present? ? Wiki.titleize(page) : nil + parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil + url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, + :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) + end + end + link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) + else + # project or wiki doesn't exist + all + end + else + all + end + end + end + + # Redmine links + # + # Examples: + # Issues: + # #52 -> Link to issue #52 + # Changesets: + # r52 -> Link to revision 52 + # commit:a85130f -> Link to scmid starting with a85130f + # Documents: + # document#17 -> Link to document with id 17 + # document:Greetings -> Link to the document with title "Greetings" + # document:"Some document" -> Link to the document with title "Some document" + # Versions: + # version#3 -> Link to version with id 3 + # version:1.0.0 -> Link to version named "1.0.0" + # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" + # Attachments: + # attachment:file.zip -> Link to the attachment of the current object named file.zip + # Source files: + # source:some/file -> Link to the file located at /some/file in the project's repository + # source:some/file@52 -> Link to the file's revision 52 + # source:some/file#L120 -> Link to line 120 of the file + # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 + # export:some/file -> Force the download of the file + # Forum messages: + # message#1218 -> Link to message with id 1218 + # Projects: + # project:someproject -> Link to project named "someproject" + # project#3 -> Link to project with id 3 + # + # Links can refer other objects from other projects, using project identifier: + # identifier:r52 + # identifier:document:"Some document" + # identifier:version:1.0.0 + # identifier:source:some/file + def parse_redmine_links(text, default_project, obj, attr, only_path, options) + text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| + leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 + link = nil + project = default_project + if project_identifier + project = Project.visible.find_by_identifier(project_identifier) + end + if esc.nil? + if prefix.nil? && sep == 'r' + if project + repository = nil + if repo_identifier + repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} + else + repository = project.repository + end + # project.changesets.visible raises an SQL error because of a double join on repositories + if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) + link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, + :class => 'changeset', + :title => truncate_single_line(changeset.comments, :length => 100)) + end + end + elsif sep == '#' + oid = identifier.to_i + case prefix + when nil + if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) + anchor = comment_id ? "note-#{comment_id}" : nil + link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, + :class => issue.css_classes, + :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") + end + when 'document' + if document = Document.visible.find_by_id(oid) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if version = Version.visible.find_by_id(oid) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' + end + when 'message' + if message = Message.visible.find_by_id(oid, :include => :parent) + link = link_to_message(message, {:only_path => only_path}, :class => 'message') + end + when 'forum' + if board = Board.visible.find_by_id(oid) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if news = News.visible.find_by_id(oid) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'project' + if p = Project.visible.find_by_id(oid) + link = link_to_project(p, {:only_path => only_path}, :class => 'project') + end + end + elsif sep == ':' + # removes the double quotes if any + name = identifier.gsub(%r{^"(.*)"$}, "\\1") + case prefix + when 'document' + if project && document = project.documents.visible.find_by_title(name) + link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, + :class => 'document' + end + when 'version' + if project && version = project.versions.visible.find_by_name(name) + link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, + :class => 'version' + end + when 'forum' + if project && board = project.boards.visible.find_by_name(name) + link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, + :class => 'board' + end + when 'news' + if project && news = project.news.visible.find_by_title(name) + link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, + :class => 'news' + end + when 'commit', 'source', 'export' + if project + repository = nil + if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} + repo_prefix, repo_identifier, name = $1, $2, $3 + repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} + else + repository = project.repository + end + if prefix == 'commit' + 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(changeset.comments, :length => 100) + end + else + if repository && User.current.allowed_to?(:browse_repository, project) + 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), + :rev => rev, + :anchor => anchor}, + :class => (prefix == 'export' ? 'source download' : 'source') + end + end + repo_prefix = nil + end + when 'attachment' + attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) + if attachments && attachment = Attachment.latest_attach(attachments, name) + link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') + end + when 'project' + 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 + end + end + (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) + end + end + + HEADING_RE = /(]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) + + def parse_sections(text, project, obj, attr, only_path, options) + return unless options[:edit_section_links] + text.gsub!(HEADING_RE) do + heading = $1 + @current_section += 1 + if @current_section > 1 + content_tag('div', + link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), + :class => 'contextual', + :title => l(:button_edit_section), + :id => "section-#{@current_section}") + heading.html_safe + else + heading + end + end + end + + # Headings and TOC + # Adds ids and links to headings unless options[:headings] is set to false + def parse_headings(text, project, obj, attr, only_path, options) + return if options[:headings] == false + + text.gsub!(HEADING_RE) do + level, attrs, content = $2.to_i, $3, $4 + item = strip_tags(content).strip + anchor = sanitize_anchor_name(item) + # used for single-file wiki export + anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) + @heading_anchors[anchor] ||= 0 + idx = (@heading_anchors[anchor] += 1) + if idx > 1 + anchor = "#{anchor}-#{idx}" + end + @parsed_headings << [level, anchor, item] + "\n#{content}" + end + end + + MACROS_RE = /( + (!)? # escaping + ( + \{\{ # opening tag + ([\w]+) # macro name + (\(([^\n\r]*?)\))? # optional arguments + ([\n\r].*?[\n\r])? # optional block of text + \}\} # closing tag + ) + )/mx unless const_defined?(:MACROS_RE) + + MACRO_SUB_RE = /( + \{\{ + macro\((\d+)\) + \}\} + )/x unless const_defined?(:MACRO_SUB_RE) + + # Extracts macros from text + def catch_macros(text) + macros = {} + text.gsub!(MACROS_RE) do + all, macro = $1, $4.downcase + if macro_exists?(macro) || all =~ MACRO_SUB_RE + index = macros.size + macros[index] = all + "{{macro(#{index})}}" + else + all + end + end + macros + end + + # Executes and replaces macros in text + def inject_macros(text, obj, macros, execute=true) + text.gsub!(MACRO_SUB_RE) do + all, index = $1, $2.to_i + orig = macros.delete(index) + if execute && orig && orig =~ MACROS_RE + esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) + if esc.nil? + h(exec_macro(macro, obj, args, block) || all) + else + h(all) + end + elsif orig + h(orig) + else + h(all) + end + end + end + + TOC_RE = /

    \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) + + # Renders the TOC with given headings + def replace_toc(text, headings) + text.gsub!(TOC_RE) do + # Keep only the 4 first levels + headings = headings.select{|level, anchor, item| level <= 4} + if headings.empty? + '' + else + div_class = 'toc' + div_class << ' right' if $1 == '>' + div_class << ' left' if $1 == '<' + out = "

    • " + root = headings.map(&:first).min + current = root + started = false + headings.each do |level, anchor, item| + if level > current + out << '
      • ' * (level - current) + elsif level < current + out << "
      \n" * (current - level) + "
    • " + elsif started + out << '
    • ' + end + out << "#{item}" + current = level + started = true + end + out << '
    ' * (current - root) + out << '' + end + end + end + + # Same as Rails' simple_format helper without using paragraphs + def simple_format_without_paragraph(text) + text.to_s. + gsub(/\r\n?/, "\n"). # \r\n and \r -> \n + gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br + gsub(/([^\n]\n)(?=[^\n])/, '\1
    '). # 1 newline -> br + html_safe + end + + def lang_options_for_select(blank=true) + (blank ? [["(auto)", ""]] : []) + languages_options + end + + def label_tag_for(name, option_tags = nil, options = {}) + label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") + content_tag("label", label_text) + end + + def labelled_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + if args.first.is_a?(Symbol) + options.merge!(:as => args.shift) + end + options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) + form_for(*args, &proc) + end + + def labelled_fields_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) + fields_for(*args, &proc) + end + + def labelled_remote_form_for(*args, &proc) + ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) + form_for(*args, &proc) + end + + def error_messages_for(*objects) + html = "" + objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact + errors = objects.map {|o| o.errors.full_messages}.flatten + if errors.any? + html << "
      \n" + errors.each do |error| + html << "
    • #{h error}
    • \n" + end + html << "
    \n" + end + html.html_safe + end + + def delete_link(url, options={}) + options = { + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :class => 'icon icon-del' + }.merge(options) + + link_to l(:button_delete), url, options + end + + def preview_link(url, form, target='preview', options={}) + content_tag 'a', l(:label_preview), { + :href => "#", + :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, + :accesskey => accesskey(:preview) + }.merge(options) + end + + def link_to_function(name, function, html_options={}) + content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) + end + + # Helper to render JSON in views + def raw_json(arg) + arg.to_json.to_s.gsub('/', '\/').html_safe + end + + def back_url + url = params[:back_url] + if url.nil? && referer = request.env['HTTP_REFERER'] + url = CGI.unescape(referer.to_s) + end + url + end + + def back_url_hidden_field_tag + url = back_url + hidden_field_tag('back_url', url, :id => nil) unless url.blank? + end + + def check_all_links(form_name) + link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + + " | ".html_safe + + link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") + end + + def progress_bar(pcts, options={}) + pcts = [pcts, pcts] unless pcts.is_a?(Array) + pcts = pcts.collect(&:round) + pcts[1] = pcts[1] - pcts[0] + pcts << (100 - pcts[1] - pcts[0]) + width = options[:width] || '100px;' + legend = options[:legend] || '' + content_tag('table', + content_tag('tr', + (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + + (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 progress-#{pcts[0]}", :style => "width: #{width};").html_safe + + content_tag('p', legend, :class => 'percent').html_safe + end + + def checked_image(checked=true) + if checked + image_tag 'toggle_check.png' + end + end + + def context_menu(url) + unless @context_menu_included + content_for :header_tags do + javascript_include_tag('context_menu') + + stylesheet_link_tag('context_menu') + end + if l(:direction) == 'rtl' + content_for :header_tags do + stylesheet_link_tag('context_menu_rtl') + end + end + @context_menu_included = true + end + javascript_tag "contextMenuInit('#{ url_for(url) }')" + end + + def calendar_for(field_id) + include_calendar_headers_tags + javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") + end + + def include_calendar_headers_tags + unless @calendar_headers_tags_included + tags = javascript_include_tag("datepicker") + @calendar_headers_tags_included = true + content_for :header_tags do + start_of_week = Setting.start_of_week + start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? + # Redmine uses 1..7 (monday..sunday) in settings and locales + # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 + start_of_week = start_of_week.to_i % 7 + tags << javascript_tag( + "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + + path_to_image('/images/calendar.png') + + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " + + "selectOtherMonths: true, changeMonth: true, changeYear: true, " + + "beforeShow: beforeShowDatePicker};") + 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") + end + tags + end + end + end + + # Overrides Rails' stylesheet_link_tag with themes and plugins support. + # Examples: + # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults + # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets + # + def stylesheet_link_tag(*sources) + options = sources.last.is_a?(Hash) ? sources.pop : {} + plugin = options.delete(:plugin) + sources = sources.map do |source| + if plugin + "/plugin_assets/#{plugin}/stylesheets/#{source}" + elsif current_theme && current_theme.stylesheets.include?(source) + current_theme.stylesheet_path(source) + else + source + end + end + super sources, options + end + + # Overrides Rails' image_tag with themes and plugins support. + # Examples: + # image_tag('image.png') # => picks image.png from the current theme or defaults + # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets + # + def image_tag(source, options={}) + if plugin = options.delete(:plugin) + source = "/plugin_assets/#{plugin}/images/#{source}" + elsif current_theme && current_theme.images.include?(source) + source = current_theme.image_path(source) + end + super source, options + end + + # Overrides Rails' javascript_include_tag with plugins support + # Examples: + # javascript_include_tag('scripts') # => picks scripts.js from defaults + # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets + # + def javascript_include_tag(*sources) + options = sources.last.is_a?(Hash) ? sources.pop : {} + if plugin = options.delete(:plugin) + sources = sources.map do |source| + if plugin + "/plugin_assets/#{plugin}/javascripts/#{source}" + else + source + end + end + end + super sources, options + end + + # TODO: remove this in 2.5.0 + def has_content?(name) + content_for?(name) + end + + def sidebar_content? + content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present? + end + + def view_layouts_base_sidebar_hook_response + @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) + end + + def email_delivery_enabled? + !!ActionMailer::Base.perform_deliveries + end + + # Returns the avatar image tag for the given +user+ if avatars are enabled + # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') + def avatar(user, options = { }) + if Setting.gravatar_enabled? + options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) + email = nil + if user.respond_to?(:mail) + email = user.mail + elsif user.to_s =~ %r{<(.+?)>} + email = $1 + end + return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil + else + '' + end + end + + def sanitize_anchor_name(anchor) + if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' + 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*)?}, '-') + end + end + + # Returns the javascript tags that are included in the html layout head + def javascript_heads + 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 + tags + end + + def favicon + "".html_safe + end + + def robot_exclusion_tag + ''.html_safe + end + + # Returns true if arg is expected in the API response + def include_in_api_response?(arg) + unless @included_in_api_response + param = params[:include] + @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') + @included_in_api_response.collect!(&:strip) + end + @included_in_api_response.include?(arg.to_s) + end + + # Returns options or nil if nometa param or X-Redmine-Nometa header + # was set in the request + def api_meta(options) + if params[:nometa].present? || request.headers['X-Redmine-Nometa'] + # compatibility mode for activeresource clients that raise + # an error when unserializing an array with attributes + nil + else + options + end + end + + private + + def wiki_helper + helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) + extend helper + return self + end + + def link_to_content_update(text, url_params = {}, html_options = {}) + link_to(text, url_params, html_options) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/aa/aa0beb1e634d59699f850105fb8491055e09d5c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/aa/aa0beb1e634d59699f850105fb8491055e09d5c3.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,784 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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) + #replace exotic characters with their hex representation to avoid invalid filenames + trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) do |x| + codepoint = RUBY_VERSION < '1.9' ? x[0] : x.codepoints.to_a[0] + sprintf('%%%02x', codepoint) + end + "#{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 =~ (/(\w+)(\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 user 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 + + old_notified_events = Setting.notified_events + old_password_min_length = Setting.password_min_length + begin + # Turn off email notifications temporarily + Setting.notified_events = [] + Setting.password_min_length = 4 + # Run the migration + TracMigrate.migrate + ensure + # Restore previous settings + Setting.notified_events = old_notified_events + Setting.password_min_length = old_password_min_length + end + end +end + diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/aa/aaf905ee3518e0c52f64e7a528de1f2b7f2001eb.svn-base --- a/.svn/pristine/aa/aaf905ee3518e0c52f64e7a528de1f2b7f2001eb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 '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 - - def test_index - get :index - assert_response :success - assert_template 'edit' - end - - def test_get_edit - get :edit - assert_response :success - assert_template 'edit' - - assert_tag 'input', :attributes => {:name => 'settings[enabled_scm][]', :value => ''} - end - - def test_get_edit_should_preselect_default_issue_list_columns - with_settings :issue_list_default_columns => %w(tracker subject status updated_on) do - get :edit - assert_response :success - end - - assert_select 'select[id=selected_columns][name=?]', 'settings[issue_list_default_columns][]' do - assert_select 'option', 4 - assert_select 'option[value=tracker]', :text => 'Tracker' - assert_select 'option[value=subject]', :text => 'Subject' - assert_select 'option[value=status]', :text => 'Status' - assert_select 'option[value=updated_on]', :text => 'Updated' - end - - assert_select 'select[id=available_columns]' do - assert_select 'option[value=tracker]', 0 - assert_select 'option[value=priority]', :text => 'Priority' - end - end - - def test_get_edit_without_trackers_should_succeed - Tracker.delete_all - - get :edit - assert_response :success - end - - def test_post_edit_notifications - post :edit, :settings => {:mail_from => 'functional@test.foo', - :bcc_recipients => '0', - :notified_events => %w(issue_added issue_updated news_added), - :emails_footer => 'Test footer' - } - assert_redirected_to '/settings/edit' - assert_equal 'functional@test.foo', Setting.mail_from - assert !Setting.bcc_recipients? - assert_equal %w(issue_added issue_updated news_added), Setting.notified_events - assert_equal 'Test footer', Setting.emails_footer - Setting.clear_cache - end - - def test_get_plugin_settings - Setting.stubs(:plugin_foo).returns({'sample_setting' => 'Plugin setting value'}) - ActionController::Base.append_view_path(File.join(Rails.root, "test/fixtures/plugins")) - Redmine::Plugin.register :foo do - settings :partial => "foo_plugin/foo_plugin_settings" - end - - get :plugin, :id => 'foo' - assert_response :success - assert_template 'plugin' - assert_tag 'form', :attributes => {:action => '/settings/plugin/foo'}, - :descendant => {:tag => 'input', :attributes => {:name => 'settings[sample_setting]', :value => 'Plugin setting value'}} - - Redmine::Plugin.clear - end - - def test_get_invalid_plugin_settings - get :plugin, :id => 'none' - assert_response 404 - end - - def test_post_plugin_settings - Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true) - Redmine::Plugin.register(:foo) {} - - post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} - assert_redirected_to '/settings/plugin/foo' - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab06de66fee50f24871dafa010661fea7e1b86f1.svn-base --- a/.svn/pristine/ab/ab06de66fee50f24871dafa010661fea7e1b86f1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class VersionsController < ApplicationController - menu_item :roadmap - model_object Version - before_filter :find_model_object, :except => [:index, :new, :create, :close_completed] - before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed] - before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed] - before_filter :authorize - - accept_api_auth :index, :show, :create, :update, :destroy - - helper :custom_fields - helper :projects - - def index - respond_to do |format| - format.html { - @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] - - @versions = @project.shared_versions || [] - @versions += @project.rolled_up_versions.visible if @with_subprojects - @versions = @versions.uniq.sort - unless params[:completed] - @completed_versions = @versions.select {|version| version.closed? || version.completed? } - @versions -= @completed_versions - end - - @issues_by_version = {} - if @selected_tracker_ids.any? && @versions.any? - issues = Issue.visible.all( - :include => [:project, :status, :tracker, :priority, :fixed_version], - :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)}, - :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id" - ) - @issues_by_version = issues.group_by(&:fixed_version) - end - @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} - } - format.api { - @versions = @project.shared_versions.all - } - end - end - - def show - respond_to do |format| - format.html { - @issues = @version.fixed_issues.visible. - includes(:status, :tracker, :priority). - reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id"). - all - } - format.api - end - end - - def new - @version = @project.versions.build - @version.safe_attributes = params[:version] - - respond_to do |format| - format.html - format.js - end - end - - def create - @version = @project.versions.build - if params[:version] - attributes = params[:version].dup - attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing']) - @version.safe_attributes = attributes - end - - if request.post? - if @version.save - respond_to do |format| - format.html do - flash[:notice] = l(:notice_successful_create) - redirect_back_or_default settings_project_path(@project, :tab => 'versions') - end - format.js - format.api do - render :action => 'show', :status => :created, :location => version_url(@version) - end - end - else - respond_to do |format| - format.html { render :action => 'new' } - format.js { render :action => 'new' } - format.api { render_validation_errors(@version) } - end - end - end - end - - def edit - end - - def update - if request.put? && params[:version] - attributes = params[:version].dup - attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) - @version.safe_attributes = attributes - if @version.save - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_back_or_default settings_project_path(@project, :tab => 'versions') - } - format.api { render_api_ok } - end - else - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@version) } - end - end - end - end - - def close_completed - if request.put? - @project.close_completed_versions - end - 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 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 settings_project_path(@project, :tab => 'versions') - } - format.api { head :unprocessable_entity } - end - end - end - - def status_by - respond_to do |format| - format.html { render :action => 'show' } - format.js - end - end - - private - - def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) - if ids = params[:tracker_ids] - @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } - else - @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab114f5adab85b76c8331bf71ea599dc558c656f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab114f5adab85b76c8331bf71ea599dc558c656f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,106 @@ +== Redmine installation + +Redmine - project management software +Copyright (C) 2006-2014 Jean-Philippe Lang +http://www.redmine.org/ + + +== Requirements + +* 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.7) + * SQLServer (tested with SQLServer 2012) + +Optional: +* SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) +* ImageMagick (to enable Gantt export to png images) + +== Installation + +1. Uncompress the program archive + +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`. + +5. 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_secret_token + +6. 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. + +7. 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. + + Assuming you run Redmine with a user named "redmine": + sudo chown -R redmine:redmine files log tmp public/plugin_assets + sudo chmod -R 755 files log tmp public/plugin_assets + +8. Test the installation by running the WEBrick web server + + Under the main application directory run: + ruby script/rails server -e production + + Once WEBrick has started, point your browser to http://localhost:3000/ + You should now see the application welcome page. + +9. 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab1a5cb004277811c44708d7b2f380ff38057bbc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab1a5cb004277811c44708d7b2f380ff38057bbc.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,75 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'diff' + +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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab219cc423e55ab4791b83323c88cebbfd8a0fc8.svn-base --- a/.svn/pristine/ab/ab219cc423e55ab4791b83323c88cebbfd8a0fc8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 => /\A(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) - if revisions - identifiers = revisions.collect(&:identifier).compact - changesets.where(:revision => identifiers).reorder("committed_on DESC").includes(:repository, :user).all - else - [] - end - 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 - - protected - - def load_entries_changesets(entries) - return unless entries - - entries_with_identifier = entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?} - identifiers = entries_with_identifier.map {|entry| entry.lastrev.identifier}.compact.uniq - - if identifiers.any? - changesets_by_identifier = changesets.where(:revision => identifiers).includes(:user, :repository).all.group_by(&:revision) - entries_with_identifier.each do |entry| - if m = changesets_by_identifier[entry.lastrev.identifier] - entry.changeset = m.first - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab2ac870d79ffb5e7c22bd5bd53c01cd412b73f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab2ac870d79ffb5e7c22bd5bd53c01cd412b73f1.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,173 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab3ccb1256c2398242468237819b498aec32eb54.svn-base --- a/.svn/pristine/ab/ab3ccb1256c2398242468237819b498aec32eb54.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class IssueCustomField < CustomField - has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id" - has_and_belongs_to_many :trackers, :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id" - has_many :issues, :through => :issue_custom_values - - def type_name - :label_issue_plural - end -end - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab5241095b4d64374cabe95fece10b1972c8081e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/ab5241095b4d64374cabe95fece10b1972c8081e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,228 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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}" + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) + 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.where(cond).find(params[:query_id]) + 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 = nil + @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab6d26ef7a0d1797ec9850f4776a0b64ecc44d36.svn-base --- a/.svn/pristine/ab/ab6d26ef7a0d1797ec9850f4776a0b64ecc44d36.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

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

    - -<%= labelled_form_for @tracker do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab765c6a0235f6d883edf2ecc8ab4daf55689a05.svn-base --- a/.svn/pristine/ab/ab765c6a0235f6d883edf2ecc8ab4daf55689a05.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 CustomFieldUserFormatTest < ActiveSupport::TestCase - fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues - - def setup - @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') - 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.users.sort.collect(&:id).map(&:to_s), possible_values - end - - def test_possible_values_with_nil_project_resource - project = Project.find(1) - 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.users.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.users & projects.last.users).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 - user = @field.cast_value("2") - assert_kind_of User, user - assert_equal User.find(2), user - end - - def test_cast_invalid_value - assert_equal nil, @field.cast_value("187") - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/ab96afdc85199bd90097700415e66f01fff19c6b.svn-base --- a/.svn/pristine/ab/ab96afdc85199bd90097700415e66f01fff19c6b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,474 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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.find(:all, :conditions => {:admin => true}).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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/aba2cd51a5b23e23b844b712e307db11aefced78.svn-base --- a/.svn/pristine/ab/aba2cd51a5b23e23b844b712e307db11aefced78.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -

    <%=l(:label_reported_issues)%> (<%= Issue.visible.count(:conditions => { :author_id => User.current.id }) %>)

    - -<% reported_issues = issuesreportedbyme_items %> -<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> -<% if reported_issues.length > 0 %> -

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

    -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, - {:controller => 'issues', :action => 'index', :set_filter => 1, - :author_id => 'me', :format => 'atom', :key => User.current.rss_key}, - {:title => l(:label_reported_issues)}) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ab/abce92f16794eda42d844c3ac0c0ab72c8c6b0ec.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ab/abce92f16794eda42d844c3ac0c0ab72c8c6b0ec.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,362 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac29d85a760bdd05295d465c5da481729f76a3a3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ac/ac29d85a760bdd05295d465c5da481729f76a3a3.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,216 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, + :projects_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_equal [], 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_equal [], r.errors[:base] + end + + def test_create_should_make_journal_entry + from = Issue.find(1) + to = Issue.find(2) + from_journals = from.journals.size + to_journals = to.journals.size + relation = IssueRelation.new(:issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES) + assert relation.save + from.reload + to.reload + relation.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_precedes', from.journals.last.details.last.prop_key + assert_equal '2', from.journals.last.details.last.value + assert_nil from.journals.last.details.last.old_value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_follows', to.journals.last.details.last.prop_key + assert_equal '1', to.journals.last.details.last.value + assert_nil to.journals.last.details.last.old_value + end + + def test_delete_should_make_journal_entry + relation = IssueRelation.find(1) + id = relation.id + from = relation.issue_from + to = relation.issue_to + from_journals = from.journals.size + to_journals = to.journals.size + assert relation.destroy + from.reload + to.reload + assert_equal from.journals.size, (from_journals + 1) + assert_equal to.journals.size, (to_journals + 1) + assert_equal 'relation', from.journals.last.details.last.property + assert_equal 'label_blocks', from.journals.last.details.last.prop_key + assert_equal '9', from.journals.last.details.last.old_value + assert_nil from.journals.last.details.last.value + assert_equal 'relation', to.journals.last.details.last.property + assert_equal 'label_blocked_by', to.journals.last.details.last.prop_key + assert_equal '10', to.journals.last.details.last.old_value + assert_nil to.journals.last.details.last.value + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac29e4943c6b7f06073e396c00b1a9f4120db8e8.svn-base --- a/.svn/pristine/ac/ac29e4943c6b7f06073e396c00b1a9f4120db8e8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +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} %> - <% if User.current.allowed_to?(:export_wiki_pages, @project) %> - <%= f.link_to('PDF', :url => {:action => 'export', :format => 'pdf'}) %> - <%= f.link_to('HTML', :url => {:action => 'export'}) %> - <% end %> -<% 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac2df139756a1b383082a659ee11216bfec62b21.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ac/ac2df139756a1b383082a659ee11216bfec62b21.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,477 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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) + Journal.preload_journals_details_custom_fields(@journals) + # TODO: use #select! when ruby1.8 support is dropped + @journals.reject! {|journal| !journal.notes? && journal.visible_details.empty?} + @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) + @relation = IssueRelation.new + + 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 = save_issue_with_child_records + 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.visible}.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(:&) + + @issue_params = params[:issue] || {} + @issue_params[:custom_field_values] ||= {} + end + + def bulk_update + @issues.sort! + @copy = params[:copy].present? + attributes = parse_params_for_bulk_issue_attributes(params) + + unsaved_issues = [] + saved_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 |orig_issue| + orig_issue.reload + if @copy + issue = orig_issue.copy({}, + :attachments => params[:copy_attachments].present?, + :subtasks => params[:copy_subtasks].present? + ) + else + issue = orig_issue + 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 + saved_issues << issue + else + unsaved_issues << orig_issue + end + end + + if unsaved_issues.empty? + flash[:notice] = l(:notice_successful_update) unless saved_issues.empty? + if params[:follow] + if @issues.size == 1 && saved_issues.size == 1 + redirect_to issue_path(saved_issues.first) + elsif saved_issues.map(&:project).uniq.size == 1 + redirect_to project_issues_path(saved_issues.map(&:project).first) + end + else + redirect_back_or_default _project_issues_path(@project) + end + else + @saved_issues = @issues + @unsaved_issues = unsaved_issues + @issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id)) + bulk_edit + render :action => 'bulk_edit' + end + end + + def destroy + @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).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, @issue.new_record?) + @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 + + # Saves @issue and a time_entry from the parameters + def save_issue_with_child_records + Issue.transaction do + if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project) + time_entry = @time_entry || TimeEntry.new + time_entry.project = @issue.project + time_entry.issue = @issue + time_entry.user = User.current + time_entry.spent_on = User.current.today + time_entry.attributes = params[:time_entry] + @issue.time_entries << time_entry + end + + call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + if @issue.save + call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal}) + else + raise ActiveRecord::Rollback + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac3a2ee58099c23648eac79beac2eb4ee2a7c013.svn-base --- a/.svn/pristine/ac/ac3a2ee58099c23648eac79beac2eb4ee2a7c013.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac3cb3eefdd851a5f9d663d9f434eeb017054b9d.svn-base --- a/.svn/pristine/ac/ac3cb3eefdd851a5f9d663d9f434eeb017054b9d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# 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. - -production: - adapter: mysql - database: redmine - host: localhost - username: root - password: "" - encoding: utf8 - -development: - adapter: mysql - database: redmine_development - host: localhost - username: root - password: "" - encoding: utf8 - -# Warning: The database defined as "test" will be erased and -# 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 - database: redmine_test - host: localhost - username: root - password: "" - encoding: utf8 - -test_pgsql: - adapter: postgresql - database: redmine_test - host: localhost - username: postgres - password: "postgres" - -test_sqlite3: - adapter: sqlite3 - database: db/test.sqlite3 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/ac3d46576ac0d49645b8babc84dcdd0e6f305a12.svn-base --- a/.svn/pristine/ac/ac3d46576ac0d49645b8babc84dcdd0e6f305a12.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, :trackers - - def test_sorted_scope - assert_equal Role.all.sort, Role.sorted.all - end - - def test_givable_scope - assert_equal Role.all.reject(&:builtin?).sort, Role.givable.all - end - - def test_builtin_scope - assert_equal Role.all.select(&:builtin?).sort, Role.builtin(true).all.sort - assert_equal Role.all.reject(&:builtin?).sort, Role.builtin(false).all.sort - end - - def test_copy_from - role = Role.find(1) - copy = Role.new.copy_from(role) - - assert_nil copy.id - assert_equal '', copy.name - assert_equal role.permissions, copy.permissions - - copy.name = 'Copy' - assert copy.save - end - - def test_copy_workflows - source = Role.find(1) - assert_equal 90, source.workflow_rules.size - - target = Role.new(:name => 'Target') - assert target.save - target.workflow_rules.copy(source) - target.reload - assert_equal 90, target.workflow_rules.size - end - - def test_permissions_should_be_unserialized_with_its_coder - Role::PermissionsAttributeCoder.expects(:load).once - Role.find(1).permissions - 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 - - def test_find_all_givable - assert_equal Role.all.reject(&:builtin?).sort, Role.find_all_givable - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ac/acb16fb7d1a43ca9b265d3fc139a9a8c3d61e7c6.svn-base --- a/.svn/pristine/ac/acb16fb7d1a43ca9b265d3fc139a9a8c3d61e7c6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class SearchController < ApplicationController - before_filter :find_optional_project - - helper :messages - include MessagesHelper - - def index - @question = params[:q] || "" - @question.strip! - @all_words = params[:all_words] ? params[:all_words].present? : true - @titles_only = params[:titles_only] ? params[:titles_only].present? : false - - projects_to_search = - case params[:scope] - when 'all' - nil - when 'my_projects' - User.current.memberships.collect(&:project) - when 'subprojects' - @project ? (@project.self_and_descendants.active.all) : nil - else - @project - end - - offset = nil - 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 - return - end - - @object_types = Redmine::Search.available_search_types.dup - if projects_to_search.is_a? Project - # don't search projects - @object_types.delete('projects') - # only show what the user is allowed to view - @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} - end - - @scope = @object_types.select {|t| params[t]} - @scope = @object_types if @scope.empty? - - # extract tokens from the question - # eg. hello "bye bye" => ["hello", "bye bye"] - @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} - # tokens must be at least 2 characters long - @tokens = @tokens.uniq.select {|w| w.length > 1 } - - if !@tokens.empty? - # no more than 5 tokens to search for - @tokens.slice! 5..-1 if @tokens.size > 5 - - @results = [] - @results_by_type = Hash.new {|h,k| h[k] = 0} - - limit = 10 - @scope.each do |s| - r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search, - :all_words => @all_words, - :titles_only => @titles_only, - :limit => (limit+1), - :offset => offset, - :before => params[:previous].nil?) - @results += r - @results_by_type[s] += c - end - @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} - if params[:previous].nil? - @pagination_previous_date = @results[0].event_datetime if offset && @results[0] - if @results.size > limit - @pagination_next_date = @results[limit-1].event_datetime - @results = @results[0, limit] - end - else - @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] - if @results.size > limit - @pagination_previous_date = @results[-(limit)].event_datetime - @results = @results[-(limit), limit] - end - end - else - @question = "" - end - render :layout => false if request.xhr? - end - -private - def find_optional_project - return true unless params[:id] - @project = Project.find(params[:id]) - check_project_privacy - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad0238c786fdc47f0ed2113b9d3c0fe464953281.svn-base --- a/.svn/pristine/ad/ad0238c786fdc47f0ed2113b9d3c0fe464953281.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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, request, response, &block) - builder = case format - when 'xml', :xml; Builders::Xml.new(request, response) - when 'json', :json; Builders::Json.new(request, response) - else; raise "No builder for format #{format}" - end - if block - block.call(builder) - else - builder - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad194258daac897f0c8e0281fc155c7b77d1ff1e.svn-base --- a/.svn/pristine/ad/ad194258daac897f0c8e0281fc155c7b77d1ff1e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad198b4ca217e676200add6d2e30b0345b1e653c.svn-base --- a/.svn/pristine/ad/ad198b4ca217e676200add6d2e30b0345b1e653c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad1b2e21afd9705f8ae7a1d13601f98473cd206b.svn-base --- a/.svn/pristine/ad/ad1b2e21afd9705f8ae7a1d13601f98473cd206b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad7462fa3902df8825690cb1e165b92bb8407c2c.svn-base --- a/.svn/pristine/ad/ad7462fa3902df8825690cb1e165b92bb8407c2c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -<%= 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 :partial => 'queries/columns', - :locals => { - :query => Query.new(:column_names => Setting.issue_list_default_columns), - :tag_name => 'settings[issue_list_default_columns][]' - } %> -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ad9161bbe4944ca1db8ea296d48b7287d41e3113.svn-base --- a/.svn/pristine/ad/ad9161bbe4944ca1db8ea296d48b7287d41e3113.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 Searchable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Options: - # * :columns - a column or an array of columns to search - # * :project_key - project foreign key (default to project_id) - # * :date_column - name of the datetime column (default to created_on) - # * :sort_order - name of the column used to sort results (default to :date_column or created_on) - # * :permission - permission required to search the model (default to :view_"objects") - def acts_as_searchable(options = {}) - return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods) - - cattr_accessor :searchable_options - self.searchable_options = options - - if searchable_options[:columns].nil? - raise 'No searchable column defined.' - elsif !searchable_options[:columns].is_a?(Array) - searchable_options[:columns] = [] << searchable_options[:columns] - end - - searchable_options[:project_key] ||= "#{table_name}.project_id" - searchable_options[:date_column] ||= "#{table_name}.created_on" - searchable_options[:order_column] ||= searchable_options[:date_column] - - # Should we search custom fields on this model ? - searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil? - - send :include, Redmine::Acts::Searchable::InstanceMethods - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Searches the model for the given tokens - # projects argument can be either nil (will search all projects), a project or an array of projects - # Returns the results and the results count - def search(tokens, projects=nil, options={}) - if projects.is_a?(Array) && projects.empty? - # no results - return [[], 0] - end - - # TODO: make user an argument - user = User.current - tokens = [] << tokens unless tokens.is_a?(Array) - projects = [] << projects unless projects.nil? || projects.is_a?(Array) - - limit_options = {} - limit_options[:limit] = options[:limit] if options[:limit] - - columns = searchable_options[:columns] - columns = columns[0..0] if options[:titles_only] - - token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} - - if !options[:titles_only] && searchable_options[:search_custom_fields] - 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 ?" + - " AND #{CustomValue.table_name}.custom_field_id IN (#{searchable_custom_field_ids.join(',')}))" - token_clauses << custom_field_sql - end - end - - sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') - - tokens_conditions = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - - scope = self.scoped - project_conditions = [] - if searchable_options.has_key?(:permission) - project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project) - elsif respond_to?(:visible) - scope = scope.visible(user) - else - ActiveSupport::Deprecation.warn "acts_as_searchable with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." - project_conditions << Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym) - end - # TODO: use visible scope options instead - project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? - project_conditions = project_conditions.empty? ? nil : project_conditions.join(' AND ') - - results = [] - results_count = 0 - - 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 - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/ada6049cb9e3f42d297d010fa449795702a9f12a.svn-base --- a/.svn/pristine/ad/ada6049cb9e3f42d297d010fa449795702a9f12a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -

    <%=l(:label_issue_new)%>

    - -<%= call_hook(:view_issues_new_top, {:issue => @issue}) %> - -<%= labelled_form_for @issue, :url => project_issues_path(@project), - :html => {:id => 'issue-form', :multipart => true} do |f| %> - <%= error_messages_for 'issue' %> - <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %> -
    -
    - <%= render :partial => 'issues/form', :locals => {:f => f} %> -
    - - <% if @copy_from && @copy_from.attachments.any? %> -

    - - <%= check_box_tag 'copy_attachments', '1', @copy_attachments %> -

    - <% end %> - <% if @copy_from && !@copy_from.leaf? %> -

    - - <%= check_box_tag 'copy_subtasks', '1', @copy_subtasks %> -

    - <% end %> - -

    <%= render :partial => 'attachments/form', :locals => {:container => @issue} %>

    - - <% if @issue.safe_attribute? 'watcher_user_ids' -%> -

    - - <%= watchers_checkboxes(@issue, @available_watchers) %> - - - <%= link_to l(:label_search_for_watchers), - {:controller => 'watchers', :action => 'new', :project_id => @issue.project}, - :remote => true, - :method => 'get' %> - -

    - <% end %> -
    - - <%= submit_tag l(:button_create) %> - <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> - <%= preview_link preview_new_issue_path(:project_id => @project), 'issue-form' %> - - <%= javascript_tag "$('#issue_subject').focus();" %> -<% end %> - -
    - -<% content_for :header_tags do %> - <%= robot_exclusion_tag %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/adb25c6fe998d19f312cf127b845819361f33fb5.svn-base --- a/.svn/pristine/ad/adb25c6fe998d19f312cf127b845819361f33fb5.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module RolesHelper -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ad/add68a0cfda9664dcb00c6400a024de641fa88b8.svn-base --- a/.svn/pristine/ad/add68a0cfda9664dcb00c6400a024de641fa88b8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Document < ActiveRecord::Base - include Redmine::SafeAttributes - belongs_to :project - belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" - 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| 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| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) - } - - safe_attributes 'category_id', 'title', 'description' - - def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_documents, project) - end - - def initialize(attributes=nil, *args) - super - if new_record? - self.category ||= DocumentCategory.default - end - end - - def updated_on - unless @updated_on - a = attachments.last - @updated_on = (a && a.created_on) || created_on - end - @updated_on - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/ae1f195964d1ff8b740bcbc54f3dee15904d207f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/ae1f195964d1ff8b740bcbc54f3dee15904d207f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,662 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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" + FELIX_HEX = "Felix Sch\xC3\xA4fer" + 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 + @felix_utf8 = FELIX_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @felix_utf8.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_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '57ca437c' + assert_select "+ td.author", :text => "jsmith" do + assert_select "+ td", + :text => "test-#{@char_1}.txt" + end + end + end + end + end + end + end + + def test_annotate_latin_1_author + ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1| + get :annotate, :id => PRJ_ID, + :path => repository_path_hash([" filename with a leading space.txt "])[:param], + :rev => r1 + assert_select "th.line-num", :text => '1' do + assert_select "+ td.revision" do + assert_select "a", :text => '83ca5fd5' + assert_select "+ td.author", :text => @felix_utf8 do + assert_select "+ td", + :text => "And this is a file with a leading and trailing space..." + end + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/ae90afe2979961b1a71c92c215405e87e9e7ab30.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/ae90afe2979961b1a71c92c215405e87e9e7ab30.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module MailHandlerHelper +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/ae9b39171caeb6f8f6a13fdb73ccbb11ad433254.svn-base --- a/.svn/pristine/ae/ae9b39171caeb6f8f6a13fdb73ccbb11ad433254.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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.find(:all, :limit => 1000, :order => 'path').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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/aeb7461845d55dcd77e6813155582781f1a0ad2a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/aeb7461845d55dcd77e6813155582781f1a0ad2a.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/aebfff37f858adc52ca91629ed8d12be26a654bf.svn-base --- a/.svn/pristine/ae/aebfff37f858adc52ca91629ed8d12be26a654bf.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 IssueStatusTest < ActiveSupport::TestCase - fixtures :issue_statuses, :issues, :roles, :trackers - - def test_create - status = IssueStatus.new :name => "Assigned" - assert !status.save - # status name uniqueness - assert_equal 1, status.errors.count - - status.name = "Test Status" - assert status.save - assert !status.is_default - end - - def test_destroy - status = IssueStatus.find(3) - assert_difference 'IssueStatus.count', -1 do - assert status.destroy - end - assert_nil WorkflowTransition.first(:conditions => {:old_status_id => status.id}) - assert_nil WorkflowTransition.first(:conditions => {:new_status_id => status.id}) - end - - def test_destroy_status_in_use - # Status assigned to an Issue - status = Issue.find(1).status - assert_raise(RuntimeError, "Can't delete status") { status.destroy } - end - - def test_default - status = IssueStatus.default - assert_kind_of IssueStatus, status - end - - def test_change_default - status = IssueStatus.find(2) - assert !status.is_default - status.is_default = true - assert status.save - status.reload - - assert_equal status, IssueStatus.default - assert !IssueStatus.find(1).is_default - end - - def test_reorder_should_not_clear_default_status - status = IssueStatus.default - status.move_to_bottom - status.reload - assert status.is_default? - end - - def test_new_statuses_allowed_to - WorkflowTransition.delete_all - - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) - WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true) - WorkflowTransition.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) - - assert_equal [2], status.new_statuses_allowed_to([role], tracker, false, false).map(&:id) - assert_equal [2], status.find_new_statuses_allowed_to([role], tracker, false, false).map(&:id) - - assert_equal [2, 3, 5], status.new_statuses_allowed_to([role], tracker, true, false).map(&:id) - assert_equal [2, 3, 5], status.find_new_statuses_allowed_to([role], tracker, true, false).map(&:id) - - assert_equal [2, 4, 5], status.new_statuses_allowed_to([role], tracker, false, true).map(&:id) - assert_equal [2, 4, 5], status.find_new_statuses_allowed_to([role], tracker, false, true).map(&:id) - - assert_equal [2, 3, 4, 5], status.new_statuses_allowed_to([role], tracker, true, true).map(&:id) - assert_equal [2, 3, 4, 5], status.find_new_statuses_allowed_to([role], tracker, true, true).map(&:id) - end - - def test_update_done_ratios_with_issue_done_ratio_set_to_issue_field_should_change_nothing - IssueStatus.find(1).update_attribute(:default_done_ratio, 50) - - with_settings :issue_done_ratio => 'issue_field' do - IssueStatus.update_issue_done_ratios - assert_equal 0, Issue.count(:conditions => {:done_ratio => 50}) - end - end - - def test_update_done_ratios_with_issue_done_ratio_set_to_issue_status_should_update_issues - IssueStatus.find(1).update_attribute(:default_done_ratio, 50) - - with_settings :issue_done_ratio => 'issue_status' do - IssueStatus.update_issue_done_ratios - issues = Issue.all(:conditions => {:status_id => 1}) - assert_equal [50], issues.map {|issue| issue.read_attribute(:done_ratio)}.uniq - end - end - - def test_sorted_scope - assert_equal IssueStatus.all.sort, IssueStatus.sorted.all - end - - def test_named_scope - status = IssueStatus.named("resolved").first - assert_not_nil status - assert_equal "Resolved", status.name - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/aec5b584466e9e60b9cc8f97f0d1ae1d90e9fe28.svn-base --- a/.svn/pristine/ae/aec5b584466e9e60b9cc8f97f0d1ae1d90e9fe28.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -#!/usr/bin/env ruby - -require 'net/http' -require 'net/https' -require 'uri' -require 'optparse' - -module Net - class HTTPS < HTTP - def self.post_form(url, params, headers, options={}) - request = Post.new(url.path) - request.form_data = params - request.initialize_http_header(headers) - request.basic_auth url.user, url.password if url.user - 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.2.3' - - 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 = {} - - optparse = OptionParser.new do |opts| - opts.banner = "Usage: rdm-mailhandler.rb [options] --url= --key=" - opts.separator("") - opts.separator("Reads an email from standard input and forward it to a Redmine server through a HTTP request.") - opts.separator("") - opts.separator("Required arguments:") - opts.on("-u", "--url URL", "URL of the Redmine server") {|v| self.url = v} - opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v} - opts.separator("") - opts.separator("General options:") - 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", - "API key (use this option instead of --key", - "if you don't the key to appear in the", - "command line)") {|v| read_key_from_file(v)} - opts.on("--no-check-certificate", "do not check server certificate") {self.no_check_certificate = true} - opts.on("-h", "--help", "show this help") {puts opts; exit 1} - 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} - opts.on("-t", "--tracker TRACKER", "name of the target tracker") {|v| self.issue_attributes['tracker'] = v} - opts.on( "--category CATEGORY", "name of the target category") {|v| self.issue_attributes['category'] = v} - opts.on( "--priority PRIORITY", "name of the target priority") {|v| self.issue_attributes['priority'] = v} - opts.on("-o", "--allow-override ATTRS", "allow email content to override attributes", - "specified by previous options", - "ATTRS is a comma separated list of attributes") {|v| self.allow_override = v} - opts.separator("") - opts.separator("Examples:") - opts.separator("No project specified. Emails MUST contain the 'Project' keyword:") - opts.separator(" rdm-mailhandler.rb --url http://redmine.domain.foo --key secret") - opts.separator("") - opts.separator("Fixed project and default tracker specified, but emails can override") - opts.separator("both tracker and priority attributes using keywords:") - opts.separator(" rdm-mailhandler.rb --url https://domain.foo/redmine --key secret \\") - opts.separator(" --project foo \\") - opts.separator(" --tracker bug \\") - opts.separator(" --allow-override tracker,priority") - - opts.summary_width = 27 - end - optparse.parse! - - unless url && key - puts "Some arguments are missing. Use `rdm-mailhandler.rb --help` for getting help." - exit 1 - end - 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, - '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}..." - 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 - 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 - - def read_key_from_file(filename) - begin - self.key = File.read(filename).strip - rescue Exception => e - $stderr.puts "Unable to read the key from #{filename}:\n#{e.message}" - exit 1 - end - end -end - -handler = RedmineMailHandler.new -exit(handler.submit(STDIN.read)) diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ae/aeded9a36e99b80726b5792b98ad75a12b089d12.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ae/aeded9a36e99b80726b5792b98ad75a12b089d12.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,81 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/af/af12a7c7b0cc0f8267262dce6f48ac239ee40dc3.svn-base --- a/.svn/pristine/af/af12a7c7b0cc0f8267262dce6f48ac239ee40dc3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,206 +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 might require some additional configuration. See the guides at: -# http://www.redmine.org/projects/redmine/wiki/EmailConfiguration -# -# production: -# email_delivery: -# delivery_method: :smtp -# smtp_settings: -# 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: - - # 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. - # - # 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: - - # Set this to false to disable plugins' assets mirroring on startup. - # You can use `rake redmine:plugins:assets` to manually mirror assets - # to public/plugin_assets when you install/upgrade a Redmine plugin. - # - #mirror_plugins_assets_on_startup: false - - # 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. - # - # If you have a load-balancing Redmine cluster, you have to use the - # same secret token on each machine. - #secret_token: 'change it to a long random string' - - # Absolute path (e.g. /usr/bin/convert, c:/im/convert.exe) to - # the ImageMagick's `convert` binary. Used to generate attachment thumbnails. - #imagemagick_convert_command: - - # Configuration of RMagcik font. - # - # Redmine uses RMagcik in order to export gantt png. - # You don't need this setting if you don't install RMagcik. - # - # In CJK (Chinese, Japanese and Korean), - # in order to show CJK characters correctly, - # you need to set this configuration. - # - # Because there is no standard font across platforms in CJK, - # you need to set a font installed in your server. - # - # This setting is not necessary in non CJK. - # - # Examples for Japanese: - # Windows: - # rmagick_font_path: C:\windows\fonts\msgothic.ttc - # Linux: - # rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf - # - 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: - -# specific configuration options for development environment -# that overrides the default ones -development: diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/af/af84f3f5537a5370b6f58104816b1886cf576cfc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/af/af84f3f5537a5370b6f58104816b1886cf576cfc.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,294 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/af/af8d1c47c0b7f932347cefaec91e6d658168c7f9.svn-base --- a/.svn/pristine/af/af8d1c47c0b7f932347cefaec91e6d658168c7f9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

    <%= link_to(h("#{issue.tracker.name} ##{issue.id}: #{issue.subject}"), issue_url) %>

    - -<%= render_email_issue_attributes(issue, true) %> - -<%= textilizable(issue, :description, :only_path => false) %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/af/af96c0777d172b6c3630823b529d0bf5cada5ec3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/af/af96c0777d172b6c3630823b529d0bf5cada5ec3.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,77 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 CustomFieldUserFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') + 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.users.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_with_nil_project_resource + project = Project.find(1) + 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.users.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.users & projects.last.users).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 + user = @field.cast_value("2") + assert_kind_of User, user + assert_equal User.find(2), user + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b0017ec26c9070575c2b102bc7db593493d5910c.svn-base --- a/.svn/pristine/b0/b0017ec26c9070575c2b102bc7db593493d5910c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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| { :include => :project, - :conditions => 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b00a202757b193101a54d08689fd79809fe86c1b.svn-base --- a/.svn/pristine/b0/b00a202757b193101a54d08689fd79809fe86c1b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -<%= error_messages_for 'auth_source' %> - -
    - -

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

    - -

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

    -
    - - - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b01b1d9455c0cc103023931e45cd6cdc1d9c80a4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b01b1d9455c0cc103023931e45cd6cdc1d9c80a4.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,210 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class UsersController < ApplicationController + layout 'admin' + + before_filter :require_admin, :except => :show + before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] + accept_api_auth :index, :show, :create, :update, :destroy + + helper :sort + include SortHelper + helper :custom_fields + include CustomFieldsHelper + + def index + sort_init 'login', 'asc' + sort_update %w(login firstname lastname mail admin created_on last_login_on) + + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + + @status = params[:status] || 1 + + scope = User.logged.status(@status) + scope = scope.like(params[:name]) if params[:name].present? + scope = scope.in_group(params[:group_id]) if params[:group_id].present? + + @user_count = scope.count + @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 { + @groups = Group.all.sort + render :layout => !request.xhr? + } + format.api + end + end + + def show + # show projects based on current user visibility + @memberships = @user.memberships.where(Project.visible_condition(User.current)).all + + events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) + @events_by_day = events.group_by(&:event_date) + + unless User.current.admin? + if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?) + render_404 + return + end + end + + respond_to do |format| + format.html { render :layout => 'base' } + format.api + end + end + + def new + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] + @auth_sources = AuthSource.all + end + + def create + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.safe_attributes = params[:user] + @user.admin = params[:user][:admin] || false + @user.login = params[:user][:login] + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id + + if @user.save + @user.pref.attributes = params[:pref] + @user.pref.save + + Mailer.account_information(@user, @user.password).deliver if params[:send_information] + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) + if params[:continue] + attrs = params[:user].slice(:generate_password) + redirect_to new_user_path(:user => attrs) + else + redirect_to edit_user_path(@user) + end + } + format.api { render :action => 'show', :status => :created, :location => user_url(@user) } + end + else + @auth_sources = AuthSource.all + # Clear password input + @user.password = @user.password_confirmation = nil + + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@user) } + end + end + end + + def edit + @auth_sources = AuthSource.all + @membership ||= Member.new + end + + def update + @user.admin = params[:user][:admin] if params[:user][:admin] + @user.login = params[:user][:login] if params[:user][:login] + if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) + @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] + end + @user.safe_attributes = params[:user] + # Was the account actived ? (do it before User#save clears the change) + was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) + # TODO: Similar to My#account + @user.pref.attributes = params[:pref] + + if @user.save + @user.pref.save + + if was_activated + Mailer.account_activated(@user).deliver + elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? + Mailer.account_information(@user, @user.password).deliver + end + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to_referer_or edit_user_path(@user) + } + format.api { render_api_ok } + end + else + @auth_sources = AuthSource.all + @membership ||= Member.new + # Clear password input + @user.password = @user.password_confirmation = nil + + respond_to do |format| + format.html { render :action => :edit } + format.api { render_validation_errors(@user) } + end + end + end + + def destroy + @user.destroy + respond_to do |format| + format.html { redirect_back_or_default(users_path) } + format.api { render_api_ok } + end + end + + def edit_membership + @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) + @membership.save + respond_to do |format| + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } + format.js + end + end + + def destroy_membership + @membership = Member.find(params[:membership_id]) + if @membership.deletable? + @membership.destroy + end + respond_to do |format| + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } + format.js + end + end + + private + + def find_user + if params[:id] == 'current' + require_login || return + @user = User.current + else + @user = User.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b028ab37b0e71e8b5e428fa2705253f8e96293a8.svn-base --- a/.svn/pristine/b0/b028ab37b0e71e8b5e428fa2705253f8e96293a8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - before_filter :build_new_custom_field, :only => [:new, :create] - before_filter :find_custom_field, :only => [:edit, :update, :destroy] - - def index - @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name } - @tab = params[:tab] || 'IssueCustomField' - end - - def new - end - - def create - 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 custom_fields_path(:tab => @custom_field.class.name) - else - render :action => 'new' - end - end - - def edit - end - - def update - 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 custom_fields_path(:tab => @custom_field.class.name) - else - render :action => 'edit' - end - end - - def destroy - 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 - - def build_new_custom_field - @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 - - def find_custom_field - @custom_field = CustomField.find(params[:id]) - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b02c60eec1517017e4be344103ca5e9fef268321.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b02c60eec1517017e4be344103ca5e9fef268321.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + include Redmine::I18n + + 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 + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Darcs.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::Darcs.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) + 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.filechanges.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.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 + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b068921bb34f750331c44877f44180d7da10bc0f.svn-base --- a/.svn/pristine/b0/b068921bb34f750331c44877f44180d7da10bc0f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +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_check_box :unsubscribe %>

    - -

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

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

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

    -

    <%= setting_select :session_timeout, [[l(:label_disabled), 0]] + [1, 2, 4, 8, 12, 24, 48].collect{|hours| [l('datetime.distance_in_words.x_hours', :count => hours), (hours * 60).to_s]} %>

    -
    - -

    <%= l(:text_session_expiration_settings) %>

    -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b06985145680863895954e4da4419cc45ad4d7ad.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b06985145680863895954e4da4419cc45ad4d7ad.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 RoutingReportsTest < ActionController::IntegrationTest + def test_reports + assert_routing( + { :method => 'get', :path => "/projects/567/issues/report" }, + { :controller => 'reports', :action => 'issue_report', :id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/issues/report/assigned_to" }, + { :controller => 'reports', :action => 'issue_report_details', + :id => '567', :detail => 'assigned_to' } + ) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b080561727eb14f1a6a2cf717727d6923d2488b6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b080561727eb14f1a6a2cf717727d6923d2488b6.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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/setup' + + 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 + + def test_root_url_path + to_test = { + ':pserver:cvs_user:cvs_password@123.456.789.123:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@123.456.789.123/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server:9876/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/repo' => '/repo', + ':pserver:cvs_user:cvs_password@cvs_server/path/repo' => '/path/repo', + ':ext:cvsservername:/path' => '/path' + } + + to_test.each do |string, expected| + assert_equal expected, Redmine::Scm::Adapters::CvsAdapter.new('foo', string).send(:root_url_path), "#{string} failed" + 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 "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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b08b8359ba61a19a3531e1766fc73fb2077273a2.svn-base --- a/.svn/pristine/b0/b08b8359ba61a19a3531e1766fc73fb2077273a2.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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, - :validate => false - send :include, Redmine::Acts::Customizable::InstanceMethods - validate :validate_custom_field_values - 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) - values = values.stringify_keys - - custom_field_values.each do |custom_field_value| - key = custom_field_value.custom_field_id.to_s - if values.has_key?(key) - value = values[key] - if value.is_a?(Array) - value = value.reject(&:blank?).uniq - if value.empty? - value << '' - end - end - custom_field_value.value = value - end - end - @custom_field_values_changed = true - end - - def custom_field_values - @custom_field_values ||= available_custom_fields.collect do |field| - x = CustomFieldValue.new - x.custom_field = field - x.customized = self - if field.multiple? - values = custom_values.select { |v| v.custom_field == field } - if values.empty? - values << custom_values.build(:customized => self, :custom_field => field, :value => nil) - end - x.value = values.map(&:value) - else - cv = custom_values.detect { |v| v.custom_field == field } - cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil) - x.value = cv.value - end - x - end - 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 custom_field_value(c) - field_id = (c.is_a?(CustomField) ? c.id : c.to_i) - custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value) - end - - def validate_custom_field_values - if new_record? || custom_field_values_changed? - custom_field_values.each(&:validate_value) - end - end - - def save_custom_field_values - target_custom_values = [] - custom_field_values.each do |custom_field_value| - if custom_field_value.value.is_a?(Array) - custom_field_value.value.each do |v| - target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field && cv.value == v} - target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field, :value => v) - target_custom_values << target - end - else - target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field} - target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field) - target.value = custom_field_value.value - target_custom_values << target - end - end - self.custom_values = target_custom_values - custom_values.each(&:save) - @custom_field_values_changed = false - true - end - - def reset_custom_values! - @custom_field_values = nil - @custom_field_values_changed = true - end - - module ClassMethods - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b0bccc208d7807b84f7eb5ca0db0c75765ec033a.svn-base --- a/.svn/pristine/b0/b0bccc208d7807b84f7eb5ca0db0c75765ec033a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b0dd7f2e6e380d76ce0d76b315917633345e67d4.svn-base --- a/.svn/pristine/b0/b0dd7f2e6e380d76ce0d76b315917633345e67d4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -
    -<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time-add' %> -
    - -<%= render_timelog_breadcrumb %> - -

    <%= l(:label_spent_time) %>

    - -<%= form_tag({:controller => 'timelog', :action => 'report', - :project_id => @project, :issue_id => @issue}, - :method => :get, :id => 'query_form') do %> - <% @report.criteria.each do |criterion| %> - <%= hidden_field_tag 'criteria[]', criterion, :id => nil %> - <% end %> - <%= render :partial => 'timelog/date_range' %> - -

    : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], - [l(:label_month), 'month'], - [l(:label_week), 'week'], - [l(:label_day_plural).titleize, 'day']], @report.columns), - :onchange => "this.form.submit();" %> - - : <%= select_tag('criteria[]', options_for_select([[]] + (@report.available_criteria.keys - @report.criteria).collect{|k| [l_or_humanize(@report.available_criteria[k][:label]), k]}), - :onchange => "this.form.submit();", - :style => 'width: 200px', - :id => nil, - :disabled => (@report.criteria.length >= 3), :id => "criterias") %> - <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @report.columns}, :class => 'icon icon-reload' %>

    -<% end %> - -<% unless @report.criteria.empty? %> -
    -

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

    -
    - -<% unless @report.hours.empty? %> -
    - - - -<% @report.criteria.each do |criteria| %> - -<% end %> -<% columns_width = (40 / (@report.periods.length+1)).to_i %> -<% @report.periods.each do |period| %> - -<% end %> - - - - -<%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %> - - - <%= ('' * (@report.criteria.size - 1)).html_safe %> - <% total = 0 -%> - <% @report.periods.each do |period| -%> - <% sum = sum_hours(select_hours(@report.hours, @report.columns, period.to_s)); total += sum -%> - - <% end -%> - - - -
    <%= l_or_humanize(@report.available_criteria[criteria][:label]) %><%= period %><%= l(:label_total) %>
    <%= l(:label_total) %><%= html_hours("%.2f" % sum) if sum > 0 %><%= html_hours("%.2f" % total) if total > 0 %>
    -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'CSV', :url => params %> -<% end %> -<% end %> -<% end %> - -<% html_title l(:label_spent_time), l(:label_report) %> - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b0/b0e82d7d19b1e5c31f1fcecc02dfe717ce20175c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b0/b0e82d7d19b1e5c31f1fcecc02dfe717ce20175c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b1/b10cf0a6565381d9c11e7e85aa348b209e42548a.svn-base --- a/.svn/pristine/b1/b10cf0a6565381d9c11e7e85aa348b209e42548a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ ---- -users_004: - created_on: 2006-07-19 19:34:07 +02:00 - status: 1 - last_login_on: - language: en - # password = foo - salt: 3126f764c3c5ac61cbfc103f25f934cf - hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b - updated_on: 2006-07-19 19:34:07 +02:00 - admin: false - mail: rhill@somenet.foo - lastname: Hill - firstname: Robert - id: 4 - auth_source_id: - mail_notification: all - login: rhill - type: User -users_001: - created_on: 2006-07-19 19:12:21 +02:00 - status: 1 - last_login_on: 2006-07-19 22:57:52 +02:00 - language: en - # password = admin - salt: 82090c953c4a0000a7db253b0691a6b4 - hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 - updated_on: 2006-07-19 22:57:52 +02:00 - admin: true - mail: admin@somenet.foo - lastname: Admin - firstname: redMine - id: 1 - auth_source_id: - mail_notification: all - login: admin - type: User -users_002: - created_on: 2006-07-19 19:32:09 +02:00 - status: 1 - last_login_on: 2006-07-19 22:42:15 +02:00 - language: en - # password = jsmith - salt: 67eb4732624d5a7753dcea7ce0bb7d7d - hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc - updated_on: 2006-07-19 22:42:15 +02:00 - admin: false - mail: jsmith@somenet.foo - lastname: Smith - firstname: John - id: 2 - auth_source_id: - mail_notification: all - login: jsmith - type: User -users_003: - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: en - # password = foo - salt: 7599f9963ec07b5a3b55b354407120c0 - hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: dlopper@somenet.foo - lastname: Lopper - firstname: Dave - id: 3 - auth_source_id: - mail_notification: all - login: dlopper - type: User -users_005: - id: 5 - created_on: 2006-07-19 19:33:19 +02:00 - # Locked - status: 3 - last_login_on: - language: en - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: dlopper2@somenet.foo - lastname: Lopper2 - firstname: Dave2 - auth_source_id: - mail_notification: all - login: dlopper2 - type: User -users_006: - id: 6 - created_on: 2006-07-19 19:33:19 +02:00 - status: 0 - last_login_on: - language: '' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: '' - lastname: Anonymous - firstname: '' - auth_source_id: - mail_notification: only_my_events - login: '' - type: AnonymousUser -users_007: - id: 7 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: '' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: someone@foo.bar - lastname: One - firstname: Some - auth_source_id: - mail_notification: only_my_events - login: someone - type: User -users_008: - id: 8 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: 'it' - # password = foo - salt: 7599f9963ec07b5a3b55b354407120c0 - hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: miscuser8@foo.bar - lastname: Misc - firstname: User - auth_source_id: - mail_notification: only_my_events - login: miscuser8 - type: User -users_009: - id: 9 - created_on: 2006-07-19 19:33:19 +02:00 - status: 1 - last_login_on: - language: 'it' - hashed_password: 1 - updated_on: 2006-07-19 19:33:19 +02:00 - admin: false - mail: miscuser9@foo.bar - lastname: Misc - firstname: User - auth_source_id: - mail_notification: only_my_events - login: miscuser9 - type: User -groups_010: - id: 10 - lastname: A Team - type: Group -groups_011: - id: 11 - lastname: B Team - type: Group - - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b1/b10fb2a7d79af038e911673a9e805ba72e9f4421.svn-base --- a/.svn/pristine/b1/b10fb2a7d79af038e911673a9e805ba72e9f4421.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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(', ') %> - <%= form_for(:membership, :remote => true, - :url => user_membership_path(@user, membership), :method => :put, - :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' - %> - <%= delete_link user_membership_path(@user, membership), :remote => true if membership.deletable? %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    -<% if projects.any? %> -
    <%=l(:label_project_new)%> -<%= form_for(:membership, :remote => true, :url => user_memberships_path(@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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b1/b18eaa15a5db0d11bdab1de8144265ea1093fcca.svn-base --- a/.svn/pristine/b1/b18eaa15a5db0d11bdab1de8144265ea1093fcca.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::JsonTest < ActiveSupport::TestCase - - def test_hash - assert_json_output({'person' => {'name' => 'Ryan', 'age' => 32}}) do |b| - b.person do - b.name 'Ryan' - b.age 32 - end - end - end - - def test_hash_hash - assert_json_output({'person' => {'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b| - b.person do - b.name 'Ryan' - b.birth :city => 'London', :country => 'UK' - end - end - - assert_json_output({'person' => {'id' => 1, 'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b| - b.person :id => 1 do - b.name 'Ryan' - b.birth :city => 'London', :country => 'UK' - end - end - end - - def test_array - assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b| - b.array :books do |b| - b.book :title => 'Book 1', :author => 'B. Smith' - b.book :title => 'Book 2', :author => 'G. Cooper' - end - end - - assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b| - b.array :books do |b| - b.book :title => 'Book 1' do - b.author 'B. Smith' - end - b.book :title => 'Book 2' do - b.author 'G. Cooper' - end - end - end - end - - def test_array_with_content_tags - assert_json_output({'books' => [{'value' => 'Book 1', 'author' => 'B. Smith'}, {'value' => 'Book 2', 'author' => 'G. Cooper'}]}) 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_json_output({'books' => [{'authors' => ['B. Smith', 'G. 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_json_output(expected, &block) - builder = Redmine::Views::Builders::Json.new(ActionDispatch::TestRequest.new, ActionDispatch::TestResponse.new) - block.call(builder) - assert_equal(expected, ActiveSupport::JSON.decode(builder.output)) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b1/b1e2d95840db59650e1901526ee33ceb0d395bd8.svn-base --- a/.svn/pristine/b1/b1e2d95840db59650e1901526ee33ceb0d395bd8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1084 +0,0 @@ -mn: - direction: ltr - jquery: - locale: "en" - 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_hours: - one: "1 hour" - other: "%{count} hours" - 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 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_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_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}" - 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}) - 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: 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b1/b1f1c9a5937a6d164b84ffb6897cf36e2e131495.svn-base --- a/.svn/pristine/b1/b1f1c9a5937a6d164b84ffb6897cf36e2e131495.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -

    <%= 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 %> -
    - -<%= form_tag(:action => 'bulk_update') do %> -<%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id)}.join.html_safe %> -
    -
    -

    - - <%= text_field :time_entry, :issue_id, :size => 6 %> -

    - -

    - - <%= text_field :time_entry, :spent_on, :size => 10 %><%= calendar_for('time_entry_spent_on') %> -

    - -

    - - <%= text_field :time_entry, :hours, :size => 6 %> -

    - - <% if @available_activities.any? %> -

    - - <%= select_tag('time_entry[activity_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_activities, :id, :name)) %> -

    - <% end %> - -

    - - <%= text_field(:time_entry, :comments, :size => 100) %> -

    - - <% @custom_fields.each do |custom_field| %> -

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

    - <% end %> - - <%= call_hook(:view_time_entries_bulk_edit_details_bottom, { :time_entries => @time_entries }) %> -
    -
    - -

    <%= submit_tag l(:button_submit) %>

    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b206ba598e9ac15edc3848e43e3c17a1d4cc2ebb.svn-base --- a/.svn/pristine/b2/b206ba598e9ac15edc3848e43e3c17a1d4cc2ebb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,644 +0,0 @@ -# -*- 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_with_display_subprojects_issues_to_false_should_not_include_subproject_entries - entry = TimeEntry.generate!(:project => Project.find(3)) - - with_settings :display_subprojects_issues => '0' do - get :index, :project_id => 'ecookbook' - assert_response :success - assert_template 'index' - assert_not_include entry, assigns(:entries) - end - end - - def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries - entry = TimeEntry.generate!(:project => Project.find(3)) - - with_settings :display_subprojects_issues => '0' do - get :index, :project_id => 'ecookbook', :subproject_id => 3 - assert_response :success - assert_template 'index' - assert_include entry, assigns(:entries) - end - 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_with_time_entry_custom_field_sorting - field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field') - TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'}) - TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'}) - TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'}) - field_name = "cf_#{field.id}" - - get :index, :c => ["hours", field_name], :sort => field_name - assert_response :success - assert_include field_name.to_sym, assigns(:query).column_names - assert_select "th a.sort", :text => 'String Field' - - # Make sure that values are properly sorted - values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact - assert_equal 3, values.size - assert_equal values.sort, values - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b26e895abc1075fe545ee5bc24c16e5071122bf7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b2/b26e895abc1075fe545ee5bc24c16e5071122bf7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,80 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b276250e954ac8b398b0737a04404d7c1ed1df94.svn-base --- a/.svn/pristine/b2/b276250e954ac8b398b0737a04404d7c1ed1df94.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -<%= 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, :disabled => @custom_field.multiple && !@custom_field.new_record? %>

    -<% 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 %> - -<% unless @custom_field.format_in? 'user', 'version' %> -

    <%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : 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 %>

    - -<% 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) %> -
    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b279683e40e793804fbad932586afda247f91b94.svn-base --- a/.svn/pristine/b2/b279683e40e793804fbad932586afda247f91b94.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -<%= error_messages_for 'user' %> - -
    - -
    -
    - <%=l(:label_information_plural)%> -

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

    -

    <%= 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.each do |value| %> -

    <%= custom_field_tag_with_label :user, value %>

    - <% end %> - -

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

    - <%= call_hook(:view_users_form, :user => @user, :form => f) %> -
    - -
    - <%=l(:label_authentication)%> - <% unless @auth_sources.empty? %> -

    <%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %>

    - <% end %> -
    -

    <%= f.password_field :password, :required => true, :size => 25 %> - <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    -

    <%= f.password_field :password_confirmation, :required => true, :size => 25 %>

    -
    -
    -
    - -
    -
    - <%=l(:field_mail_notification)%> - <%= render :partial => 'users/mail_notifications' %> -
    - -
    - <%=l(:label_preferences)%> - <%= render :partial => 'users/preferences' %> -
    -
    -
    -
    - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b2d23f95d6214e578deb83fcc382ea31f387d199.svn-base --- a/.svn/pristine/b2/b2d23f95d6214e578deb83fcc382ea31f387d199.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

    <%=l(:label_report_plural)%>

    - -

    <%=@report_title%>

    -<%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %> -
    -<%= link_to l(:button_back), :action => 'issue_report' %> - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b2/b2e850115d38f72b8bb02be2b155cf948264198f.svn-base --- a/.svn/pristine/b2/b2e850115d38f72b8bb02be2b155cf948264198f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingNewsTest < ActionController::IntegrationTest - def test_news_index - assert_routing( - { :method => 'get', :path => "/news" }, - { :controller => 'news', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/news.atom" }, - { :controller => 'news', :action => 'index', :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/news.xml" }, - { :controller => 'news', :action => 'index', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/news.json" }, - { :controller => 'news', :action => 'index', :format => 'json' } - ) - end - - def test_news - assert_routing( - { :method => 'get', :path => "/news/2" }, - { :controller => 'news', :action => 'show', :id => '2' } - ) - assert_routing( - { :method => 'get', :path => "/news/234" }, - { :controller => 'news', :action => 'show', :id => '234' } - ) - assert_routing( - { :method => 'get', :path => "/news/567/edit" }, - { :controller => 'news', :action => 'edit', :id => '567' } - ) - assert_routing( - { :method => 'put', :path => "/news/567" }, - { :controller => 'news', :action => 'update', :id => '567' } - ) - assert_routing( - { :method => 'delete', :path => "/news/567" }, - { :controller => 'news', :action => 'destroy', :id => '567' } - ) - end - - def test_news_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/567/news" }, - { :controller => 'news', :action => 'index', :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.atom" }, - { :controller => 'news', :action => 'index', :format => 'atom', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.xml" }, - { :controller => 'news', :action => 'index', :format => 'xml', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news.json" }, - { :controller => 'news', :action => 'index', :format => 'json', - :project_id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/news/new" }, - { :controller => 'news', :action => 'new', :project_id => '567' } - ) - assert_routing( - { :method => 'post', :path => "/projects/567/news" }, - { :controller => 'news', :action => 'create', :project_id => '567' } - ) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b3/b313db558eec0f33b4606021ff7dbfce33600948.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b3/b313db558eec0f33b4606021ff7dbfce33600948.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,186 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 RoutingWikiTest < ActionController::IntegrationTest + def test_wiki_matching + assert_routing( + { :method => 'get', :path => "/projects/567/wiki" }, + { :controller => 'wiki', :action => 'show', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/lalala" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'lalala' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/lalala.pdf" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'lalala', :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" }, + { :controller => 'wiki', :action => 'diff', :project_id => '1', + :id => 'CookBook_documentation' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" }, + { :controller => 'wiki', :action => 'diff', :project_id => '1', + :id => 'CookBook_documentation', :version => '2' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/annotate" }, + { :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 + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/date_index" }, + { :controller => 'wiki', :action => 'date_index', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/export" }, + { :controller => 'wiki', :action => 'export', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/export.pdf" }, + { :controller => 'wiki', :action => 'export', :project_id => '567', :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index" }, + { :controller => 'wiki', :action => 'index', :project_id => '567' } + ) + end + + def test_wiki_resources + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page/edit" }, + { :controller => 'wiki', :action => 'edit', :project_id => '567', + :id => 'my_page' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" }, + { :controller => 'wiki', :action => 'history', :project_id => '1', + :id => 'CookBook_documentation' } + ) + assert_routing( + { :method => 'get', :path => "/projects/22/wiki/ladida/rename" }, + { :controller => 'wiki', :action => 'rename', :project_id => '22', + :id => 'ladida' } + ) + ["post", "put"].each do |method| + assert_routing( + { :method => method, :path => "/projects/567/wiki/CookBook_documentation/preview" }, + { :controller => 'wiki', :action => 'preview', :project_id => '567', + :id => 'CookBook_documentation' } + ) + end + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/rename" }, + { :controller => 'wiki', :action => 'rename', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/protect" }, + { :controller => 'wiki', :action => 'protect', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" }, + { :controller => 'wiki', :action => 'add_attachment', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/22/wiki/ladida" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '22', + :id => 'ladida' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/22/wiki/ladida/3" }, + { :controller => 'wiki', :action => 'destroy_version', :project_id => '22', + :id => 'ladida', :version => '3' } + ) + end + + def test_api + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'show', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.xml" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2.json" }, + { :controller => 'wiki', :action => 'show', :project_id => '1', + :id => 'CookBook_documentation', :version => '2', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index.xml" }, + { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/wiki/index.json" }, + { :controller => 'wiki', :action => 'index', :project_id => '567', :format => 'json' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'update', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/567/wiki/my_page.xml" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '567', + :id => 'my_page', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/567/wiki/my_page.json" }, + { :controller => 'wiki', :action => 'destroy', :project_id => '567', + :id => 'my_page', :format => 'json' } + ) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b3/b316d8599200244900100c9290ba9b6619bf99d4.svn-base --- a/.svn/pristine/b3/b316d8599200244900100c9290ba9b6619bf99d4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b3/b34008d72f69a2170aed046a9caabca7a17faaac.svn-base --- a/.svn/pristine/b3/b34008d72f69a2170aed046a9caabca7a17faaac.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1106 +0,0 @@ -# Danish translation file for standard Ruby on Rails internationalization -# by Lars Hoeg (http://www.lenio.dk/) -# updated and upgraded to 0.9 by Morten Krogh Andersen (http://www.krogh.net) - -da: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%e. %b %Y" - long: "%e. %B %Y" - - day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag] - abbr_day_names: [sø, ma, ti, 'on', to, fr, lø] - month_names: [~, januar, februar, marts, april, maj, juni, juli, august, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%e. %B %Y, %H:%M" - time: "%H:%M" - short: "%e. %b %Y, %H:%M" - long: "%A, %e. %B %Y, %H:%M" - am: "" - pm: "" - - support: - array: - sentence_connector: "og" - skip_last_comma: true - - datetime: - distance_in_words: - half_a_minute: "et halvt minut" - less_than_x_seconds: - one: "mindre end et sekund" - other: "mindre end %{count} sekunder" - x_seconds: - one: "et sekund" - other: "%{count} sekunder" - less_than_x_minutes: - one: "mindre end et minut" - other: "mindre end %{count} minutter" - x_minutes: - one: "et minut" - other: "%{count} minutter" - about_x_hours: - one: "cirka en time" - other: "cirka %{count} timer" - x_hours: - one: "1 time" - other: "%{count} timer" - x_days: - one: "en dag" - other: "%{count} dage" - about_x_months: - one: "cirka en måned" - other: "cirka %{count} måneder" - x_months: - one: "en måned" - other: "%{count} måneder" - about_x_years: - one: "cirka et år" - other: "cirka %{count} år" - over_x_years: - one: "mere end et år" - other: "mere end %{count} år" - almost_x_years: - one: "næsten 1 år" - other: "næsten %{count} år" - - number: - format: - separator: "," - delimiter: "." - precision: 3 - currency: - format: - format: "%u %n" - unit: "DKK" - separator: "," - delimiter: "." - precision: 2 - 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" - percentage: - format: - # separator: - delimiter: "" - # precision: - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "er ikke i listen" - exclusion: "er reserveret" - invalid: "er ikke gyldig" - confirmation: "stemmer ikke overens" - accepted: "skal accepteres" - empty: "må ikke udelades" - blank: "skal udfyldes" - too_long: "er for lang (højst %{count} tegn)" - too_short: "er for kort (mindst %{count} tegn)" - wrong_length: "har forkert længde (skulle være %{count} tegn)" - taken: "er allerede anvendt" - not_a_number: "er ikke et tal" - greater_than: "skal være større end %{count}" - greater_than_or_equal_to: "skal være større end eller lig med %{count}" - equal_to: "skal være lig med %{count}" - less_than: "skal være mindre end %{count}" - less_than_or_equal_to: "skal være mindre end eller lig med %{count}" - odd: "skal være ulige" - even: "skal være lige" - greater_than_start_date: "skal være senere end startdatoen" - not_same_project: "hører ikke til samme projekt" - circular_dependency: "Denne relation vil skabe et afhængighedsforhold" - cant_link_an_issue_with_a_descendant: "En sag kan ikke relateres til en af dens underopgaver" - - template: - header: - one: "En fejl forhindrede %{model} i at blive gemt" - other: "%{count} fejl forhindrede denne %{model} i at blive gemt" - body: "Der var problemer med følgende felter:" - - actionview_instancetag_blank_option: Vælg venligst - - general_text_No: 'Nej' - general_text_Yes: 'Ja' - general_text_no: 'nej' - general_text_yes: 'ja' - general_lang_name: 'Danish (Dansk)' - general_csv_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Kontoen er opdateret. - notice_account_invalid_creditentials: Ugyldig bruger og/eller kodeord - notice_account_password_updated: Kodeordet er opdateret. - notice_account_wrong_password: Forkert kodeord - notice_account_register_done: Kontoen er oprettet. For at aktivere kontoen skal du klikke på linket i den tilsendte email. - notice_account_unknown_email: Ukendt bruger. - notice_can_t_change_password: Denne konto benytter en ekstern sikkerhedsgodkendelse. Det er ikke muligt at skifte kodeord. - notice_account_lost_email_sent: En email med instruktioner til at vælge et nyt kodeord er afsendt til dig. - notice_account_activated: Din konto er aktiveret. Du kan nu logge ind. - notice_successful_create: Succesfuld oprettelse. - notice_successful_update: Succesfuld opdatering. - notice_successful_delete: Succesfuld sletning. - notice_successful_connection: Succesfuld forbindelse. - notice_file_not_found: Siden du forsøger at tilgå eksisterer ikke eller er blevet fjernet. - notice_locking_conflict: Data er opdateret af en anden bruger. - notice_not_authorized: Du har ikke adgang til denne side. - notice_email_sent: "En email er sendt til %{value}" - notice_email_error: "En fejl opstod under afsendelse af email (%{value})" - notice_feeds_access_key_reseted: Din adgangsnøgle til RSS er nulstillet. - notice_failed_to_save_issues: "Det mislykkedes at gemme %{count} sage(r) på %{total} valgt: %{ids}." - notice_no_issue_selected: "Ingen sag er valgt! Vælg venligst hvilke emner du vil rette." - notice_account_pending: "Din konto er oprettet, og afventer administrators godkendelse." - notice_default_data_loaded: Standardopsætningen er indlæst. - - error_can_t_load_default_data: "Standardopsætning kunne ikke indlæses: %{value}" - error_scm_not_found: "Adgang nægtet og/eller revision blev ikke fundet i det valgte repository." - error_scm_command_failed: "En fejl opstod under forbindelsen til det valgte repository: %{value}" - - mail_subject_lost_password: "Dit %{value} kodeord" - mail_body_lost_password: 'Klik på dette link for at ændre dit kodeord:' - mail_subject_register: "%{value} kontoaktivering" - mail_body_register: 'Klik på dette link for at aktivere din konto:' - mail_body_account_information_external: "Du kan bruge din %{value} konto til at logge ind." - mail_body_account_information: Din kontoinformation - mail_subject_account_activation_request: "%{value} kontoaktivering" - mail_body_account_activation_request: "En ny bruger (%{value}) er registreret. Godkend venligst kontoen:" - - - field_name: Navn - field_description: Beskrivelse - field_summary: Sammenfatning - field_is_required: Skal udfyldes - field_firstname: Fornavn - field_lastname: Efternavn - field_mail: Email - field_filename: Fil - field_filesize: Størrelse - field_downloads: Downloads - field_author: Forfatter - field_created_on: Oprettet - field_updated_on: Opdateret - field_field_format: Format - field_is_for_all: For alle projekter - field_possible_values: Mulige værdier - field_regexp: Regulære udtryk - field_min_length: Mindste længde - field_max_length: Største længde - field_value: Værdi - field_category: Kategori - field_title: Titel - field_project: Projekt - field_issue: Sag - field_status: Status - field_notes: Noter - field_is_closed: Sagen er lukket - field_is_default: Standardværdi - field_tracker: Type - field_subject: Emne - field_due_date: Deadline - field_assigned_to: Tildelt til - field_priority: Prioritet - field_fixed_version: Udgave - field_user: Bruger - field_role: Rolle - field_homepage: Hjemmeside - field_is_public: Offentlig - field_parent: Underprojekt af - field_is_in_roadmap: Sager vist i roadmap - field_login: Login - field_mail_notification: Email-påmindelser - field_admin: Administrator - field_last_login_on: Sidste forbindelse - field_language: Sprog - field_effective_date: Dato - field_password: Kodeord - field_new_password: Nyt kodeord - field_password_confirmation: Bekræft - field_version: Version - field_type: Type - field_host: Vært - field_port: Port - field_account: Kode - field_base_dn: Base DN - field_attr_login: Login attribut - field_attr_firstname: Fornavn attribut - field_attr_lastname: Efternavn attribut - field_attr_mail: Email attribut - field_onthefly: løbende brugeroprettelse - field_start_date: Start dato - field_done_ratio: "% færdig" - field_auth_source: Sikkerhedsmetode - field_hide_mail: Skjul min email - field_comments: Kommentar - field_url: URL - field_start_page: Startside - field_subproject: Underprojekt - field_hours: Timer - field_activity: Aktivitet - field_spent_on: Dato - field_identifier: Identifikator - field_is_filter: Brugt som et filter - field_issue_to: Beslægtede sag - field_delay: Udsættelse - field_assignable: Sager kan tildeles denne rolle - field_redirect_existing_links: Videresend eksisterende links - field_estimated_hours: Anslået tid - field_column_names: Kolonner - field_time_zone: Tidszone - field_searchable: Søgbar - field_default_value: Standardværdi - - setting_app_title: Applikationstitel - setting_app_subtitle: Applikationsundertekst - setting_welcome_text: Velkomsttekst - setting_default_language: Standardsporg - setting_login_required: Sikkerhed påkrævet - setting_self_registration: Brugeroprettelse - setting_attachment_max_size: Vedhæftede filers max størrelse - setting_issues_export_limit: Sagseksporteringsbegrænsning - setting_mail_from: Afsender-email - setting_bcc_recipients: Skjult modtager (bcc) - setting_host_name: Værtsnavn - setting_text_formatting: Tekstformatering - setting_wiki_compression: Komprimering af wiki-historik - setting_feeds_limit: Feed indholdsbegrænsning - setting_autofetch_changesets: Hent automatisk commits - setting_sys_api_enabled: Aktiver webservice for automatisk administration af repository - setting_commit_ref_keywords: Referencenøgleord - setting_commit_fix_keywords: Afslutningsnøgleord - setting_autologin: Automatisk login - setting_date_format: Datoformat - setting_time_format: Tidsformat - setting_cross_project_issue_relations: Tillad sagsrelationer på tværs af projekter - setting_issue_list_default_columns: Standardkolonner på sagslisten - setting_emails_footer: Email-fodnote - setting_protocol: Protokol - setting_user_format: Brugervisningsformat - - project_module_issue_tracking: Sagssøgning - project_module_time_tracking: Tidsstyring - project_module_news: Nyheder - project_module_documents: Dokumenter - project_module_files: Filer - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Fora - - label_user: Bruger - label_user_plural: Brugere - label_user_new: Ny bruger - label_project: Projekt - label_project_new: Nyt projekt - label_project_plural: Projekter - label_x_projects: - zero: Ingen projekter - one: 1 projekt - other: "%{count} projekter" - label_project_all: Alle projekter - label_project_latest: Seneste projekter - label_issue: Sag - label_issue_new: Opret sag - label_issue_plural: Sager - label_issue_view_all: Vis alle sager - label_issues_by: "Sager fra %{value}" - label_issue_added: Sagen er oprettet - label_issue_updated: Sagen er opdateret - label_document: Dokument - label_document_new: Nyt dokument - label_document_plural: Dokumenter - label_document_added: Dokument tilføjet - label_role: Rolle - label_role_plural: Roller - label_role_new: Ny rolle - label_role_and_permissions: Roller og rettigheder - label_member: Medlem - label_member_new: Nyt medlem - label_member_plural: Medlemmer - label_tracker: Type - label_tracker_plural: Typer - label_tracker_new: Ny type - label_workflow: Arbejdsgang - label_issue_status: Sagsstatus - label_issue_status_plural: Sagsstatusser - label_issue_status_new: Ny status - label_issue_category: Sagskategori - label_issue_category_plural: Sagskategorier - label_issue_category_new: Ny kategori - label_custom_field: Brugerdefineret felt - label_custom_field_plural: Brugerdefinerede felter - label_custom_field_new: Nyt brugerdefineret felt - label_enumerations: Værdier - label_enumeration_new: Ny værdi - label_information: Information - label_information_plural: Information - label_please_login: Login - label_register: Registrér - label_password_lost: Glemt kodeord - label_home: Forside - label_my_page: Min side - label_my_account: Min konto - label_my_projects: Mine projekter - label_administration: Administration - label_login: Log ind - label_logout: Log ud - label_help: Hjælp - label_reported_issues: Rapporterede sager - label_assigned_to_me_issues: Sager tildelt til mig - label_last_login: Sidste logintidspunkt - label_registered_on: Registreret den - label_activity: Aktivitet - label_new: Ny - label_logged_as: Registreret som - label_environment: Miljø - label_authentication: Sikkerhed - label_auth_source: Sikkerhedsmetode - label_auth_source_new: Ny sikkerhedsmetode - label_auth_source_plural: Sikkerhedsmetoder - label_subproject_plural: Underprojekter - label_min_max_length: Min - Max længde - label_list: Liste - label_date: Dato - label_integer: Heltal - label_float: Kommatal - label_boolean: Sand/falsk - label_string: Tekst - label_text: Lang tekst - label_attribute: Attribut - label_attribute_plural: Attributter - label_no_data: Ingen data at vise - label_change_status: Ændringsstatus - label_history: Historik - label_attachment: Fil - label_attachment_new: Ny fil - label_attachment_delete: Slet fil - label_attachment_plural: Filer - label_file_added: Fil tilføjet - label_report: Rapport - label_report_plural: Rapporter - label_news: Nyheder - label_news_new: Tilføj nyheder - label_news_plural: Nyheder - label_news_latest: Seneste nyheder - label_news_view_all: Vis alle nyheder - label_news_added: Nyhed tilføjet - label_settings: Indstillinger - label_overview: Oversigt - label_version: Udgave - label_version_new: Ny udgave - label_version_plural: Udgaver - label_confirmation: Bekræftelser - label_export_to: Eksporter til - label_read: Læs... - label_public_projects: Offentlige projekter - label_open_issues: åben - label_open_issues_plural: åbne - label_closed_issues: lukket - label_closed_issues_plural: lukkede - label_x_open_issues_abbr_on_total: - zero: 0 åbne / %{total} - one: 1 åben / %{total} - other: "%{count} åbne / %{total}" - label_x_open_issues_abbr: - zero: 0 åbne - one: 1 åben - other: "%{count} åbne" - label_x_closed_issues_abbr: - zero: 0 lukkede - one: 1 lukket - other: "%{count} lukkede" - label_total: Total - label_permissions: Rettigheder - label_current_status: Nuværende status - label_new_statuses_allowed: Ny status tilladt - label_all: alle - label_none: intet - label_nobody: ingen - label_next: Næste - label_previous: Forrige - label_used_by: Brugt af - label_details: Detaljer - label_add_note: Tilføj note - label_per_page: Pr. side - label_calendar: Kalender - label_months_from: måneder frem - label_gantt: Gantt - label_internal: Intern - label_last_changes: "sidste %{count} ændringer" - label_change_view_all: Vis alle ændringer - label_personalize_page: Tilret denne side - label_comment: Kommentar - label_comment_plural: Kommentarer - label_x_comments: - zero: ingen kommentarer - one: 1 kommentar - other: "%{count} kommentarer" - label_comment_add: Tilføj en kommentar - label_comment_added: Kommentaren er tilføjet - label_comment_delete: Slet kommentar - label_query: Brugerdefineret forespørgsel - label_query_plural: Brugerdefinerede forespørgsler - label_query_new: Ny forespørgsel - label_filter_add: Tilføj filter - label_filter_plural: Filtre - label_equals: er - label_not_equals: er ikke - label_in_less_than: er mindre end - label_in_more_than: er større end - label_in: indeholdt i - label_today: i dag - label_all_time: altid - label_yesterday: i går - label_this_week: denne uge - label_last_week: sidste uge - label_last_n_days: "sidste %{count} dage" - label_this_month: denne måned - label_last_month: sidste måned - label_this_year: dette år - label_date_range: Dato interval - label_less_than_ago: mindre end dage siden - label_more_than_ago: mere end dage siden - label_ago: dage siden - label_contains: indeholder - label_not_contains: ikke indeholder - label_day_plural: dage - label_repository: Repository - label_repository_plural: Repositories - label_browse: Gennemse - label_revision: Revision - label_revision_plural: Revisioner - label_associated_revisions: Tilknyttede revisioner - label_added: tilføjet - label_modified: ændret - label_deleted: slettet - label_latest_revision: Seneste revision - label_latest_revision_plural: Seneste revisioner - label_view_revisions: Se revisioner - label_max_size: Maksimal størrelse - label_sort_highest: Flyt til toppen - label_sort_higher: Flyt op - label_sort_lower: Flyt ned - label_sort_lowest: Flyt til bunden - label_roadmap: Roadmap - label_roadmap_due_in: Deadline - label_roadmap_overdue: "%{value} forsinket" - label_roadmap_no_issues: Ingen sager i denne version - label_search: Søg - label_result_plural: Resultater - label_all_words: Alle ord - label_wiki: Wiki - label_wiki_edit: Wiki ændring - label_wiki_edit_plural: Wiki ændringer - label_wiki_page: Wiki side - label_wiki_page_plural: Wiki sider - label_index_by_title: Indhold efter titel - label_index_by_date: Indhold efter dato - label_current_version: Nuværende version - label_preview: Forhåndsvisning - label_feed_plural: Feeds - label_changes_details: Detaljer for alle ændringer - label_issue_tracking: Sagssøgning - label_spent_time: Anvendt tid - label_f_hour: "%{value} time" - label_f_hour_plural: "%{value} timer" - label_time_tracking: Tidsstyring - label_change_plural: Ændringer - label_statistics: Statistik - label_commits_per_month: Commits pr. måned - label_commits_per_author: Commits pr. bruger - label_view_diff: Vis forskelle - label_diff_inline: inline - label_diff_side_by_side: side om side - label_options: Formatering - label_copy_workflow_from: Kopier arbejdsgang fra - label_permissions_report: Godkendelsesrapport - label_watched_issues: Overvågede sager - label_related_issues: Relaterede sager - label_applied_status: Anvendte statusser - label_loading: Indlæser... - label_relation_new: Ny relation - label_relation_delete: Slet relation - label_relates_to: relaterer til - label_duplicates: duplikater - label_blocks: blokerer - label_blocked_by: blokeret af - label_precedes: kommer før - label_follows: følger - label_end_to_start: slut til start - label_end_to_end: slut til slut - label_start_to_start: start til start - label_start_to_end: start til slut - label_stay_logged_in: Forbliv indlogget - label_disabled: deaktiveret - label_show_completed_versions: Vis færdige versioner - label_me: mig - label_board: Forum - label_board_new: Nyt forum - label_board_plural: Fora - label_topic_plural: Emner - label_message_plural: Beskeder - label_message_last: Sidste besked - label_message_new: Ny besked - label_message_posted: Besked tilføjet - label_reply_plural: Besvarer - label_send_information: Send konto information til bruger - label_year: År - label_month: Måned - label_week: Uge - label_date_from: Fra - label_date_to: Til - label_language_based: Baseret på brugerens sprog - label_sort_by: "Sortér efter %{value}" - label_send_test_email: Send en test email - label_feeds_access_key_created_on: "RSS adgangsnøgle dannet for %{value} siden" - label_module_plural: Moduler - label_added_time_by: "Tilføjet af %{author} for %{age} siden" - label_updated_time: "Opdateret for %{value} siden" - label_jump_to_a_project: Skift til projekt... - label_file_plural: Filer - label_changeset_plural: Ændringer - label_default_columns: Standardkolonner - label_no_change_option: (Ingen ændringer) - label_bulk_edit_selected_issues: Masse-ret de valgte sager - label_theme: Tema - label_default: standard - label_search_titles_only: Søg kun i titler - label_user_mail_option_all: "For alle hændelser på mine projekter" - label_user_mail_option_selected: "For alle hændelser på de valgte projekter..." - label_user_mail_no_self_notified: "Jeg ønsker ikke besked om ændring foretaget af mig selv" - label_registration_activation_by_email: kontoaktivering på email - label_registration_manual_activation: manuel kontoaktivering - label_registration_automatic_activation: automatisk kontoaktivering - label_display_per_page: "Per side: %{value}" - label_age: Alder - label_change_properties: Ændre indstillinger - label_general: Generelt - label_more: Mere - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP-godkendelse - label_downloads_abbr: D/L - - button_login: Login - button_submit: Send - button_save: Gem - button_check_all: Vælg alt - button_uncheck_all: Fravælg alt - button_delete: Slet - button_create: Opret - button_test: Test - button_edit: Ret - button_add: Tilføj - button_change: Ændre - button_apply: Anvend - button_clear: Nulstil - button_lock: Lås - button_unlock: Lås op - button_download: Download - button_list: List - button_view: Vis - button_move: Flyt - button_back: Tilbage - button_cancel: Annullér - button_activate: Aktivér - button_sort: Sortér - button_log_time: Log tid - button_rollback: Tilbagefør til denne version - button_watch: Overvåg - button_unwatch: Stop overvågning - button_reply: Besvar - button_archive: Arkivér - button_unarchive: Fjern fra arkiv - button_reset: Nulstil - button_rename: Omdøb - button_change_password: Skift kodeord - button_copy: Kopiér - button_annotate: Annotér - button_update: Opdatér - button_configure: Konfigurér - - status_active: aktiv - status_registered: registreret - status_locked: låst - - text_select_mail_notifications: Vælg handlinger der skal sendes email besked for. - text_regexp_info: f.eks. ^[A-ZÆØÅ0-9]+$ - text_min_max_length_info: 0 betyder ingen begrænsninger - text_project_destroy_confirmation: Er du sikker på at du vil slette dette projekt og alle relaterede data? - text_workflow_edit: Vælg en rolle samt en type, for at redigere arbejdsgangen - text_are_you_sure: Er du sikker? - text_tip_issue_begin_day: opgaven begynder denne dag - text_tip_issue_end_day: opaven slutter denne dag - text_tip_issue_begin_end_day: opgaven begynder og slutter denne dag - text_caracters_maximum: "max %{count} karakterer." - text_caracters_minimum: "Skal være mindst %{count} karakterer lang." - text_length_between: "Længde skal være mellem %{min} og %{max} karakterer." - text_tracker_no_workflow: Ingen arbejdsgang defineret for denne type - text_unallowed_characters: Ikke-tilladte karakterer - text_comma_separated: Adskillige værdier tilladt (adskilt med komma). - text_issues_ref_in_commit_messages: Referer og løser sager i commit-beskeder - text_issue_added: "Sag %{id} er rapporteret af %{author}." - text_issue_updated: "Sag %{id} er blevet opdateret af %{author}." - text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wiki og dens indhold? - text_issue_category_destroy_question: "Nogle sager (%{count}) er tildelt denne kategori. Hvad ønsker du at gøre?" - text_issue_category_destroy_assignments: Slet tildelinger af kategori - text_issue_category_reassign_to: Tildel sager til denne kategori - text_user_mail_option: "For ikke-valgte projekter vil du kun modtage beskeder omhandlende ting du er involveret i eller overvåger (f.eks. sager du har indberettet eller ejer)." - text_no_configuration_data: "Roller, typer, sagsstatusser og arbejdsgange er endnu ikke konfigureret.\nDet er anbefalet at indlæse standardopsætningen. Du vil kunne ændre denne når den er indlæst." - text_load_default_configuration: Indlæs standardopsætningen - text_status_changed_by_changeset: "Anvendt i ændring %{value}." - text_issues_destroy_confirmation: 'Er du sikker på du ønsker at slette den/de valgte sag(er)?' - text_select_project_modules: 'Vælg moduler er skal være aktiveret for dette projekt:' - text_default_administrator_account_changed: Standardadministratorkonto ændret - text_file_repository_writable: Filarkiv er skrivbar - text_rmagick_available: RMagick tilgængelig (valgfri) - - default_role_manager: Leder - default_role_developer: Udvikler - default_role_reporter: Rapportør - default_tracker_bug: Fejl - default_tracker_feature: Funktion - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: Igangværende - default_issue_status_resolved: Løst - default_issue_status_feedback: Feedback - default_issue_status_closed: Lukket - default_issue_status_rejected: Afvist - default_doc_category_user: Brugerdokumentation - default_doc_category_tech: Teknisk dokumentation - default_priority_low: Lav - default_priority_normal: Normal - default_priority_high: Høj - default_priority_urgent: Akut - default_priority_immediate: Omgående - default_activity_design: Design - default_activity_development: Udvikling - - enumeration_issue_priorities: Sagsprioriteter - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsstyring) - - label_add_another_file: Tilføj endnu en fil - label_chronological_order: I kronologisk rækkefølge - setting_activity_days_default: Antal dage der vises under projektaktivitet - text_destroy_time_entries_question: "%{hours} timer er registreret på denne sag som du er ved at slette. Hvad vil du gøre?" - error_issue_not_found_in_project: 'Sagen blev ikke fundet eller tilhører ikke dette projekt' - text_assign_time_entries_to_project: Tildel raporterede timer til projektet - setting_display_subprojects_issues: Vis sager for underprojekter på hovedprojektet som standard - label_optional_description: Valgfri beskrivelse - text_destroy_time_entries: Slet registrerede timer - field_comments_sorting: Vis kommentar - text_reassign_time_entries: 'Tildel registrerede timer til denne sag igen' - label_reverse_chronological_order: I omvendt kronologisk rækkefølge - label_preferences: Præferencer - label_overall_activity: Overordnet aktivitet - setting_default_projects_public: Nye projekter er offentlige som standard - error_scm_annotate: "Filen findes ikke, eller kunne ikke annoteres." - label_planning: Planlægning - text_subprojects_destroy_warning: "Dets underprojekter(er): %{value} vil også blive slettet." - permission_edit_issues: Redigér sager - setting_diff_max_lines_displayed: Højeste antal forskelle der vises - permission_edit_own_issue_notes: Redigér egne noter - setting_enabled_scm: Aktiveret SCM - button_quote: Citér - permission_view_files: Se filer - permission_add_issues: Tilføj sager - permission_edit_own_messages: Redigér egne beskeder - permission_delete_own_messages: Slet egne beskeder - permission_manage_public_queries: Administrér offentlig forespørgsler - permission_log_time: Registrér anvendt tid - label_renamed: omdøbt - label_incoming_emails: Indkommende emails - permission_view_changesets: Se ændringer - permission_manage_versions: Administrér versioner - permission_view_time_entries: Se anvendt tid - label_generate_key: Generér en nøglefil - permission_manage_categories: Administrér sagskategorier - permission_manage_wiki: Administrér wiki - setting_sequential_project_identifiers: Generér sekventielle projekt-identifikatorer - setting_plain_text_mail: Emails som almindelig tekst (ingen HTML) - 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_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 - text_enumeration_category_reassign_to: 'Flyt dem til denne værdi:' - permission_select_project_modules: Vælg projektmoduler - permission_view_gantt: Se Gantt diagram - permission_delete_messages: Slet beskeder - permission_move_issues: Flyt sager - permission_edit_wiki_pages: Redigér wiki sider - label_user_activity: "%{value}'s aktivitet" - permission_manage_issue_relations: Administrér sags-relationer - label_issue_watchers: Overvågere - permission_delete_wiki_pages: Slet wiki sider - notice_unable_delete_version: Kan ikke slette versionen. - permission_view_wiki_edits: Se wiki historik - field_editable: Redigérbar - label_duplicated_by: dubleret af - permission_manage_boards: Administrér fora - permission_delete_wiki_pages_attachments: Slet filer vedhæftet wiki sider - permission_view_messages: Se beskeder - text_enumeration_destroy_question: "%{count} objekter er tildelt denne værdi." - permission_manage_files: Administrér filer - permission_add_messages: Opret beskeder - permission_edit_issue_notes: Redigér noter - permission_manage_news: Administrér nyheder - text_plugin_assets_writable: Der er skriverettigheder til plugin assets folderen - label_display: Vis - label_and_its_subprojects: "%{value} og dets underprojekter" - permission_view_calendar: Se kalender - button_create_and_continue: Opret og fortsæt - setting_gravatar_enabled: Anvend Gravatar brugerikoner - label_updated_time_by: "Opdateret af %{author} for %{age} siden" - text_diff_truncated: '... Listen over forskelle er blevet afkortet da den overstiger den maksimale størrelse der kan vises.' - text_user_wrote: "%{value} skrev:" - setting_mail_handler_api_enabled: Aktiver webservice for indkomne emails - permission_delete_issues: Slet sager - permission_view_documents: Se dokumenter - permission_browse_repository: Gennemse repository - permission_manage_repository: Administrér repository - permission_manage_members: Administrér medlemmer - mail_subject_reminder: "%{count} sag(er) har deadline i de kommende dage (%{days})" - permission_add_issue_notes: Tilføj noter - permission_edit_messages: Redigér beskeder - permission_view_issue_watchers: Se liste over overvågere - permission_commit_access: Commit adgang - setting_mail_handler_api_key: API nøgle - label_example: Eksempel - permission_rename_wiki_pages: Omdøb wiki sider - text_custom_field_possible_values_info: 'En linje for hver værdi' - permission_view_wiki_pages: Se wiki - permission_edit_project: Redigér projekt - permission_save_queries: Gem forespørgsler - label_copied: kopieret - text_repository_usernames_mapping: "Vælg eller opdatér de Redmine brugere der svarer til de enkelte brugere fundet i repository loggen.\nBrugere med samme brugernavn eller email adresse i både Redmine og det valgte repository bliver automatisk koblet sammen." - permission_edit_time_entries: Redigér tidsregistreringer - general_csv_decimal_separator: ',' - permission_edit_own_time_entries: Redigér egne tidsregistreringer - setting_repository_log_display_limit: Højeste antal revisioner vist i fil-log - setting_file_max_size_displayed: Maksimale størrelse på tekstfiler vist inline - field_watcher: Overvåger - setting_openid: Tillad OpenID login og registrering - field_identity_url: OpenID URL - label_login_with_open_id_option: eller login med OpenID - setting_per_page_options: Enheder per side muligheder - mail_body_reminder: "%{count} sage(er) som er tildelt dig har deadline indenfor de næste %{days} dage:" - field_content: Indhold - label_descending: Aftagende - label_sort: Sortér - label_ascending: Tiltagende - label_date_from_to: Fra %{start} til %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Denne side har %{descendants} underside(r) og afledte. Hvad vil du gøre? - text_wiki_page_reassign_children: Flyt undersider til denne side - text_wiki_page_nullify_children: Behold undersider som rod-sider - text_wiki_page_destroy_children: Slet undersider ogalle deres afledte sider. - setting_password_min_length: Mindste længde på kodeord - field_group_by: Gruppér resultater efter - mail_subject_wiki_content_updated: "'%{id}' wikisiden er blevet opdateret" - label_wiki_content_added: Wiki side tilføjet - mail_subject_wiki_content_added: "'%{id}' wikisiden er blevet tilføjet" - mail_body_wiki_content_added: The '%{id}' wikiside er blevet tilføjet af %{author}. - label_wiki_content_updated: Wikiside opdateret - mail_body_wiki_content_updated: Wikisiden '%{id}' er blevet opdateret af %{author}. - permission_add_project: Opret projekt - setting_new_project_user_role_id: Denne rolle gives til en bruger, som ikke er administrator, og som opretter et projekt - label_view_all_revisions: Se alle revisioner - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Der er ingen sagshåndtering for dette projekt. Kontrollér venligst projektindstillingerne. - error_no_default_issue_status: Der er ikke defineret en standardstatus. Kontrollér venligst indstillingerne (gå til "Administration -> Sagsstatusser"). - text_journal_changed: "%{label} ændret fra %{old} til %{new}" - text_journal_set_to: "%{label} sat til %{value}" - text_journal_deleted: "%{label} slettet (%{old})" - label_group_plural: Grupper - label_group: Grupper - label_group_new: Ny gruppe - label_time_entry_plural: Anvendt tid - text_journal_added: "%{label} %{value} tilføjet" - field_active: Aktiv - enumeration_system_activity: System Aktivitet - permission_delete_issue_watchers: Slet overvågere - version_status_closed: lukket - version_status_locked: låst - version_status_open: åben - error_can_not_reopen_issue_on_closed_version: En sag tildelt en lukket version kan ikke genåbnes - label_user_anonymous: Anonym - button_move_and_follow: Flyt og overvåg - setting_default_projects_modules: Standard moduler, aktiveret for nye projekter - setting_gravatar_default: Standard Gravatar billede - field_sharing: Delning - label_version_sharing_hierarchy: Med projekthierarki - label_version_sharing_system: Med alle projekter - label_version_sharing_descendants: Med underprojekter - label_version_sharing_tree: Med projekttræ - label_version_sharing_none: Ikke delt - error_can_not_archive_project: Dette projekt kan ikke arkiveres - button_duplicate: Duplikér - button_copy_and_follow: Kopiér og overvåg - label_copy_source: Kilde - setting_issue_done_ratio: Beregn sagsløsning ratio - setting_issue_done_ratio_issue_status: Benyt sagsstatus - error_issue_done_ratios_not_updated: Sagsløsnings ratio, ikke opdateret. - error_workflow_copy_target: Vælg venligst måltracker og rolle(r) - setting_issue_done_ratio_issue_field: Benyt sagsfelt - label_copy_same_as_target: Samme som mål - label_copy_target: Mål - notice_issue_done_ratios_updated: Sagsløsningsratio opdateret. - error_workflow_copy_source: Vælg venligst en kildetracker eller rolle - label_update_issue_done_ratios: Opdater sagsløsningsratio - setting_start_of_week: Start kalendre på - permission_view_issues: Vis sager - label_display_used_statuses_only: Vis kun statusser der er benyttet af denne tracker - label_revision_id: Revision %{value} - label_api_access_key: API nøgle - label_api_access_key_created_on: API nøgle genereret %{value} siden - label_feeds_access_key: RSS nøgle - notice_api_access_key_reseted: Din API nøgle er nulstillet. - setting_rest_api_enabled: Aktiver REST web service - label_missing_api_access_key: Mangler en API nøgle - label_missing_feeds_access_key: Mangler en RSS nøgle - button_show: Vis - text_line_separated: Flere væredier tilladt (en linje for hver værdi). - setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer - permission_add_subprojects: Lav underprojekter - label_subproject_new: Nyt underprojekt - text_own_membership_delete_confirmation: |- - Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter. - Er du sikker på du ønsker at fortsætte? - label_close_versions: Luk færdige versioner - label_board_sticky: Klistret - label_board_locked: Låst - permission_export_wiki_pages: Eksporter wiki sider - setting_cache_formatted_text: Cache formatteret tekst - permission_manage_project_activities: Administrer projektaktiviteter - error_unable_delete_issue_status: Det var ikke muligt at slette sagsstatus - label_profile: Profil - permission_manage_subtasks: Administrer underopgaver - field_parent_issue: Hovedopgave - label_subtask_plural: Underopgaver - label_project_copy_notifications: Send email notifikationer, mens projektet kopieres - error_can_not_delete_custom_field: Kan ikke slette brugerdefineret felt - error_unable_to_connect: Kan ikke forbinde (%{value}) - error_can_not_remove_role: Denne rolle er i brug og kan ikke slettes. - error_can_not_delete_tracker: Denne type indeholder sager og kan ikke slettes. - field_principal: Principal - label_my_page_block: blok - notice_failed_to_save_members: "Fejl under lagring af medlem(mer): %{errors}." - text_zoom_out: Zoom ud - text_zoom_in: Zoom ind - notice_unable_delete_time_entry: Kan ikke slette tidsregistrering. - label_overall_spent_time: Overordnet forbrug af tid - field_time_entries: Log tid - project_module_gantt: Gantt - project_module_calendar: Kalender - button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: %{page_title}" - field_text: Tekstfelt - label_user_mail_option_only_owner: Kun for ting jeg er ejer af - setting_default_notification_option: Standardpåmindelsesmulighed - label_user_mail_option_only_my_events: Kun for ting jeg overvåger eller er involveret i - label_user_mail_option_only_assigned: Kun for ting jeg er tildelt - label_user_mail_option_none: Ingen hændelser - field_member_of_group: Medlem af gruppe - field_assigned_to_role: Medlem af rolle - notice_not_authorized_archived_project: Projektet du prøver at tilgå, er blevet arkiveret. - label_principal_search: "Søg efter bruger eller gruppe:" - label_user_search: "Søg efter bruger:" - field_visible: Synlig - setting_commit_logtime_activity_id: Aktivitet for registreret tid - text_time_logged_by_changeset: Anvendt i changeset %{value}. - setting_commit_logtime_enabled: Aktiver tidsregistrering - notice_gantt_chart_truncated: Kortet er blevet afkortet, fordi det overstiger det maksimale antal elementer, der kan vises (%{max}) - setting_gantt_items_limit: Maksimalt antal af elementer der kan vises på gantt kortet - - 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: Kodning af Commit beskeder - 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 sag - one: 1 sag - other: "%{count} sager" - 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: alle - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Med underprojekter - label_cross_project_tree: Med projekttræ - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alle projekter - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b4/b47c06d46b32769546485279d1a354df12670495.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b4/b47c06d46b32769546485279d1a354df12670495.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b4/b4b1f198c8e086edfc312f99671cb5c7dd8f466b.svn-base --- a/.svn/pristine/b4/b4b1f198c8e086edfc312f99671cb5c7dd8f466b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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.find(: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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b4/b4e6ed08b51fdf56d73a3e5d3b099dd3bd0e68ad.svn-base --- a/.svn/pristine/b4/b4e6ed08b51fdf56d73a3e5d3b099dd3bd0e68ad.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ ---- -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 - - :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_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 - - :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 - - :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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b50241919b7b81279d644e95a3326c2189de1da8.svn-base --- a/.svn/pristine/b5/b50241919b7b81279d644e95a3326c2189de1da8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,425 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b516f333e4615d3ec7b7c90303ba24e1004720cf.svn-base --- a/.svn/pristine/b5/b516f333e4615d3ec7b7c90303ba24e1004720cf.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 VersionsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses, :issue_categories - - def setup - User.current = nil - end - - def test_index - get :index, :project_id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:versions) - # Version with no date set appears - assert assigns(:versions).include?(Version.find(3)) - # Completed version doesn't appear - assert !assigns(:versions).include?(Version.find(1)) - # Context menu on issues - assert_select "script", :text => Regexp.new(Regexp.escape("contextMenuInit('/issues/context_menu')")) - # Links to versions anchors - assert_tag 'a', :attributes => {:href => '#2.0'}, - :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}} - # Links to completed versions in the sidebar - assert_tag 'a', :attributes => {:href => '/versions/1'}, - :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}} - end - - def test_index_with_completed_versions - get :index, :project_id => 1, :completed => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:versions) - # Version with no date set appears - assert assigns(:versions).include?(Version.find(3)) - # Completed version appears - assert assigns(:versions).include?(Version.find(1)) - end - - def test_index_with_tracker_ids - get :index, :project_id => 1, :tracker_ids => [1, 3] - assert_response :success - assert_template 'index' - assert_not_nil assigns(:issues_by_version) - assert_nil assigns(:issues_by_version).values.flatten.detect {|issue| issue.tracker_id == 2} - end - - def test_index_showing_subprojects_versions - @subproject_version = Version.create!(:project => Project.find(3), :name => "Subproject version") - get :index, :project_id => 1, :with_subprojects => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:versions) - - assert assigns(:versions).include?(Version.find(4)), "Shared version not found" - assert assigns(:versions).include?(@subproject_version), "Subproject version not found" - end - - def test_index_should_prepend_shared_versions - get :index, :project_id => 1 - assert_response :success - - assert_select '#sidebar' do - assert_select 'a[href=?]', '#2.0', :text => '2.0' - assert_select 'a[href=?]', '#subproject1-2.0', :text => 'eCookbook Subproject 1 - 2.0' - end - assert_select '#content' do - assert_select 'a[name=?]', '2.0', :text => '2.0' - assert_select 'a[name=?]', 'subproject1-2.0', :text => 'eCookbook Subproject 1 - 2.0' - end - end - - def test_show - get :show, :id => 2 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:version) - - assert_tag :tag => 'h2', :content => /1.0/ - end - - def test_show_should_display_nil_counts - with_settings :default_language => 'en' do - get :show, :id => 2, :status_by => 'category' - assert_response :success - assert_select 'div#status_by' do - assert_select 'select[name=status_by]' do - assert_select 'option[value=category][selected=selected]' - end - assert_select 'a', :text => 'none' - end - end - end - - def test_new - @request.session[:user_id] = 2 - get :new, :project_id => '1' - assert_response :success - assert_template 'new' - end - - def test_new_from_issue_form - @request.session[:user_id] = 2 - 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 'Version.count' do - post :create, :project_id => '1', :version => {:name => 'test_add_version'} - end - assert_redirected_to '/projects/ecookbook/settings/versions' - version = Version.find_by_name('test_add_version') - assert_not_nil version - assert_equal 1, version.project_id - end - - def test_create_from_issue_form - @request.session[:user_id] = 2 - assert_difference 'Version.count' do - xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'} - end - version = Version.find_by_name('test_add_version_from_issue_form') - assert_not_nil version - assert_equal 1, version.project_id - - assert_response :success - assert_template 'create' - assert_equal 'text/javascript', response.content_type - assert_include 'test_add_version_from_issue_form', response.body - end - - def test_create_from_issue_form_with_failure - @request.session[:user_id] = 2 - assert_no_difference 'Version.count' do - xhr :post, :create, :project_id => '1', :version => {:name => ''} - end - assert_response :success - assert_template 'new' - assert_equal 'text/javascript', response.content_type - end - - def test_get_edit - @request.session[:user_id] = 2 - get :edit, :id => 2 - assert_response :success - assert_template 'edit' - end - - def test_close_completed - Version.update_all("status = 'open'") - @request.session[:user_id] = 2 - put :close_completed, :project_id => 'ecookbook' - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' - assert_not_nil Version.find_by_status('closed') - end - - def test_post_update - @request.session[:user_id] = 2 - put :update, :id => 2, - :version => { :name => 'New version name', - :effective_date => Date.today.strftime("%Y-%m-%d")} - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' - version = Version.find(2) - assert_equal 'New version name', version.name - assert_equal Date.today, version.effective_date - end - - def test_post_update_with_validation_failure - @request.session[:user_id] = 2 - put :update, :id => 2, - :version => { :name => '', - :effective_date => Date.today.strftime("%Y-%m-%d")} - assert_response :success - assert_template 'edit' - end - - def test_destroy - @request.session[:user_id] = 2 - assert_difference 'Version.count', -1 do - delete :destroy, :id => 3 - end - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' - assert_nil Version.find_by_id(3) - end - - def test_destroy_version_in_use_should_fail - @request.session[:user_id] = 2 - assert_no_difference 'Version.count' do - delete :destroy, :id => 2 - end - assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' - assert flash[:error].match(/Unable to delete version/) - assert Version.find_by_id(2) - end - - def test_issue_status_by - xhr :get, :status_by, :id => 2 - assert_response :success - assert_template 'status_by' - assert_template '_issue_counts' - end - - def test_issue_status_by_status - xhr :get, :status_by, :id => 2, :status_by => 'status' - assert_response :success - assert_template 'status_by' - assert_template '_issue_counts' - assert_include 'Assigned', response.body - assert_include 'Closed', response.body - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b55f3d7a207d1ee11754ddcc8223772b20e5796a.svn-base --- a/.svn/pristine/b5/b55f3d7a207d1ee11754ddcc8223772b20e5796a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

    <%=l(:label_custom_field_plural)%>

    - -<%= render_tabs custom_fields_tabs %> - -<% html_title(l(:label_custom_field_plural)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5727556fb665fb8b69db76bff48bd4ca73fc261.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b5/b5727556fb665fb8b69db76bff48bd4ca73fc261.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,1122 @@ +pt-BR: + 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 h" + time: "%H:%M h" + short: "%d/%m, %H:%M h" + long: "%A, %d de %B de %Y, %H:%M h" + 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_hours: + one: "1 hora" + other: "%{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: "quase 1 ano" + other: "quase %{count} anos" + + # 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: 3 + 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: "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" + 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ínimo: %{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" + earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues" + + 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 Atom 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:" + + + 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: Subprojeto + 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: Subtí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-chave de referência + setting_commit_fix_keywords: Definição de palavras-chave + 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: Subprojetos + label_and_its_subprojects: "%{value} e seus subprojetos" + 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_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_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: em linha + 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 Atom 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: Conjunto de alterações + label_default_columns: Colunas padrão + label_no_change_option: (Sem alteração) + label_bulk_edit_selected_issues: Edição em massa das tarefas selecionadas. + 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_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 conjunto de alterações %{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 públicas + permission_add_issues: Adicionar tarefas + permission_log_time: Adicionar tempo gasto + permission_view_changesets: Ver conjunto de alterações + 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_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 do 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 em linha + 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: Tag + label_branch: Branch + 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: bloqueado + 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 do 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 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 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 + 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 Atom + notice_api_access_key_reseted: Sua chave de acesso a API foi redefinida. + 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 Atom 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ê irá excluir algumas de suas próprias permissões e não estará mais 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: Bloqueado + 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 salvar 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}" + 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: Responsável pelo grupo + 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 conjunto de alterações %{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 gantt + 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 contém 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: Tarefa %{id} criada. + label_between: entre + setting_issue_group_assignment: Permitir atribuições de tarefas a grupos + label_diff: diff + text_git_repository_note: "Repositório esta vazio e é local (ex: /gitrepo, c:\\gitrepo)" + + description_query_sort_criteria_direction: Escolher ordenação + description_project_scope: Escopo da pesquisa + description_filter: Filtro + description_user_mail_notification: Configuração de notificações por e-mail + description_date_from: Digite 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: Campo de busca + description_notes: Notas + description_date_range_list: Escolha um período a partir da lista + description_choose_project: Projetos + description_date_to: Digite a data final + description_query_sort_criteria_attribute: Atributo de ordenação + description_wiki_subpages_reassign: Escolha uma nova página pai + description_selected_columns: Colunas selecionadas + + label_parent_revision: Pai + label_child_revision: Filho + error_scm_annotate_big_text_file: A entrada não pode ser anotada, pois excede o tamanho máximo do arquivo de texto. + setting_default_issue_start_date_to_creation_date: Usar data corrente como data inicial para novas tarefas + button_edit_section: Editar esta seção + setting_repositories_encodings: Codificação dos repositórios e anexos + description_all_columns: Todas as colunas + button_export: Exportar + label_export_options: "Opções de exportação %{export_format}" + error_attachment_too_big: Este arquivo não pode ser enviado porque excede o tamanho máximo permitido (%{max_size}) + notice_failed_to_save_time_entries: "Falha ao salvar %{count} de %{total} horas trabalhadas: %{ids}." + label_x_issues: + zero: 0 tarefa + one: 1 tarefa + other: "%{count} tarefas" + label_repository_new: Novo repositório + field_repository_is_default: Repositório principal + label_copy_attachments: Copiar anexos + label_item_position: "%{position}/%{count}" + label_completed_versions: Versões concluídas + text_project_identifier_info: Somente letras minúsculas (a-z), números, traços e sublinhados são permitidos.
    Uma vez salvo, o identificador não pode ser alterado. + field_multiple: Múltiplos valores + setting_commit_cross_project_ref: Permitir que tarefas de todos os outros projetos sejam refenciadas e resolvidas + text_issue_conflict_resolution_add_notes: Adicionar minhas anotações e descartar minhas outras mudanças + text_issue_conflict_resolution_overwrite: Aplicar as minhas alterações de qualquer maneira (notas anteriores serão mantidas, mas algumas mudanças podem ser substituídas) + notice_issue_update_conflict: A tarefa foi atualizada por um outro usuário, enquanto você estava editando. + text_issue_conflict_resolution_cancel: Descartar todas as minhas mudanças e reexibir %{link} + permission_manage_related_issues: Gerenciar tarefas relacionadas + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Procurar por outros observadores para adiconar + notice_account_deleted: Sua conta foi excluída permanentemente. + setting_unsubscribe: Permitir aos usuários excluir sua própria conta + button_delete_my_account: Excluir minha conta + text_account_destroy_confirmation: |- + Tem certeza que quer continuar? + Sua conta será excluída permanentemente, sem qualquer forma de reativá-la. + error_session_expired: A sua sessão expirou. Por favor, faça login novamente. + text_session_expiration_settings: "Aviso: a alteração dessas configurações pode expirar as sessões atuais, incluindo a sua." + setting_session_lifetime: duração máxima da sessão + setting_session_timeout: tempo limite de inatividade da sessão + label_session_expiration: "Expiração da sessão" + permission_close_project: Fechar / reabrir o projeto + label_show_closed_projects: Visualizar projetos fechados + button_close: Fechar + button_reopen: Reabrir + project_status_active: ativo + project_status_closed: fechado + project_status_archived: arquivado + text_project_closed: Este projeto está fechado e somente leitura. + 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_size: Tamanho das miniaturas (em pixels) + label_status_transitions: Estados das transições + label_fields_permissions: Permissões de campos + label_readonly: somente leitura + label_required: Obrigatório + text_repository_identifier_info: Somente letras minúsculas (az), números, traços e sublinhados são permitidos
    Uma vez salvo, o identificador não pode ser alterado. + field_board_parent: Fórum Pai + label_attribute_of_project: "Projeto %{name}" + label_attribute_of_author: "autor %{name}" + label_attribute_of_assigned_to: "atribuído a %{name}" + label_attribute_of_fixed_version: "versão %{name}" + label_copy_subtasks: Copiar subtarefas + label_copied_to: copiada + label_copied_from: copiado + label_any_issues_in_project: qualquer tarefa do projeto + label_any_issues_not_in_project: qualquer tarefa que não está no projeto + field_private_notes: notas privadas + permission_view_private_notes: Ver notas privadas + permission_set_notes_private: Permitir alterar notas para privada + label_no_issues_in_project: sem tarefas no projeto + label_any: todos + label_last_n_weeks: "últimas %{count} semanas" + setting_cross_project_subtasks: Permitir subtarefas entre projetos + label_cross_project_descendants: com subprojetos + label_cross_project_tree: Com a árvore do Projeto + label_cross_project_hierarchy: Com uma hierarquia do Projeto + label_cross_project_system: Com todos os Projetos + button_hide: Omitir + setting_non_working_week_days: dias não úteis + label_in_the_next_days: nos próximos dias + label_in_the_past_days: nos dias anteriores + label_attribute_of_user: Usuário %{name} + text_turning_multiple_off: Se você desativar vários valores, eles serão removidos, a fim de preservar somente um valor por item. + label_attribute_of_issue: Tarefa %{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: Concluído + field_generate_password: Gerar senha + setting_default_projects_tracker_ids: Tipos padrões para novos projeto + label_total_time: Total + notice_account_not_activated_yet: Sua conta ainda não foi ativada. Se você deseja receber + um novo email de ativação, por favor clique aqui. + notice_account_locked: Sua conta está bloqueada. + label_hidden: Visibilidade + label_visibility_private: para mim + label_visibility_roles: para os papéis + label_visibility_public: para qualquer usuário + field_must_change_passwd: É necessário alterar sua senha na próxima vez que tentar acessar sua conta + notice_new_password_must_be_different: A nova senha deve ser diferente da senha atual + setting_mail_handler_excluded_filenames: Exclui anexos por nome + text_convert_available: Conversor ImageMagick disponível (opcional) diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5853b637c67cfc0aed37f236eb8fa380a6714c9.svn-base --- a/.svn/pristine/b5/b5853b637c67cfc0aed37f236eb8fa380a6714c9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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={}) - content_tag("span", watcher_link(object, user), :class => watcher_css(object)) - 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')) - - 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" - 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 => 'post', :style => "vertical-align: middle", :class => "delete") - end - content << content_tag('li', s) - end - content.present? ? content_tag('ul', content) : 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5927def2130703fcd89c61c9d2aa9928f429ac5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b5/b5927def2130703fcd89c61c9d2aa9928f429ac5.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,38 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5c65a26ab892d9f8cee94e484ee432c6e022830.svn-base --- a/.svn/pristine/b5/b5c65a26ab892d9f8cee94e484ee432c6e022830.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 AccessKeys - ACCESSKEYS = {:edit => 'e', - :preview => 'r', - :quick_search => 'f', - :search => '4', - :new_issue => '7' - }.freeze unless const_defined?(:ACCESSKEYS) - - def self.key_for(action) - ACCESSKEYS[action] - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5d71b1233be49fcf1babc0bab7d8a2c992deddb.svn-base --- a/.svn/pristine/b5/b5d71b1233be49fcf1babc0bab7d8a2c992deddb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -# 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 CustomFieldsHelper - - def custom_fields_tabs - CustomField::CUSTOM_FIELDS_TABS - end - - # Return custom field html tag corresponding to its format - 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? - field_id = "#{name}_custom_field_values_#{custom_field.id}" - - tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} - - field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) - case field_format.try(:edit_as) - when "date" - text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) + - calendar_for(field_id) - when "text" - text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3)) - when "bool" - hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options) - when "list" - blank_option = ''.html_safe - unless custom_field.multiple? - if custom_field.is_required? - unless custom_field.default_value.present? - blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') - end - else - blank_option = content_tag('option') - end - end - s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), - tag_options.merge(:multiple => custom_field.multiple?)) - if custom_field.multiple? - s << hidden_field_tag(field_name, '') - end - s - else - text_field_tag(field_name, custom_value.value, tag_options) - end - end - - # Return custom field label tag - def custom_field_label_tag(name, custom_value, options={}) - required = options[:required] || custom_value.custom_field.is_required? - - content_tag "label", h(custom_value.custom_field.name) + - (required ? " *".html_safe : ""), - :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" - end - - # Return custom field tag with its label tag - def custom_field_tag_with_label(name, custom_value, options={}) - custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value) - end - - def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil) - field_name = "#{name}[custom_field_values][#{custom_field.id}]" - field_name << "[]" if custom_field.multiple? - field_id = "#{name}_custom_field_values_#{custom_field.id}" - - tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"} - - field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) - case field_format.try(:edit_as) - when "date" - text_field_tag(field_name, '', tag_options.merge(:size => 10)) + - calendar_for(field_id) - when "text" - text_area_tag(field_name, '', tag_options.merge(:rows => 3)) - when "bool" - select_tag(field_name, options_for_select([[l(:label_no_change_option), ''], - [l(:general_text_yes), '1'], - [l(:general_text_no), '0']]), tag_options) - when "list" - options = [] - options << [l(:label_no_change_option), ''] unless custom_field.multiple? - options << [l(:label_none), '__none__'] unless custom_field.is_required? - options += custom_field.possible_values_options(projects) - select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?)) - else - text_field_tag(field_name, '', tag_options) - end - end - - # Return a string used to display a custom value - def show_value(custom_value) - return "" unless custom_value - format_value(custom_value.value, custom_value.custom_field.field_format) - end - - # Return a string used to display a custom value - def format_value(value, field_format) - if value.is_a?(Array) - value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ') - else - Redmine::CustomFieldFormat.format_value(value, field_format) - end - end - - # Return an array of custom field formats which can be used in select_tag - def custom_field_formats_for_select(custom_field) - Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) - end - - # Renders the custom_values in api views - def render_api_custom_values(custom_values, api) - api.array :custom_fields do - custom_values.each do |custom_value| - attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name} - attrs.merge!(:multiple => true) if custom_value.custom_field.multiple? - api.custom_field attrs do - if custom_value.value.is_a?(Array) - api.array :value do - custom_value.value.each do |value| - api.value value unless value.blank? - end - end - else - api.value custom_value.value - end - end - end - end unless custom_values.empty? - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b5/b5ff9d7cff11555edaa362cc7b4aa84c35458cdf.svn-base --- a/.svn/pristine/b5/b5ff9d7cff11555edaa362cc7b4aa84c35458cdf.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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_form_for :custom_field, @custom_field, :url => custom_field_path(@custom_field), :html => {:method => :put, :id => 'custom_field_form'} do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b648e7261c6cf5a90c411f73fe039c9171cf83b0.svn-base --- a/.svn/pristine/b6/b648e7261c6cf5a90c411f73fe039c9171cf83b0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,372 +0,0 @@ -# -*- 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 - tests TimelogController - - 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", :criteria => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "8.65", "%.2f" % assigns(:report).total_hours - end - - def test_report_all_time - get :report, :project_id => 1, :criteria => ['project', 'issue'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "162.90", "%.2f" % assigns(:report).total_hours - end - - def test_report_all_time_by_day - get :report, :project_id => 1, :criteria => ['project', 'issue'], :columns => 'day' - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "162.90", "%.2f" % assigns(:report).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", :criteria => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "8.65", "%.2f" % assigns(:report).total_hours - end - - def test_report_two_criteria - 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) - assert_equal "162.90", "%.2f" % assigns(:report).total_hours - end - - def test_report_custom_field_criteria_with_multiple_values - field = TimeEntryCustomField.create!(:name => 'multi', :field_format => 'list', :possible_values => ['value1', 'value2']) - entry = TimeEntry.create!(:project => Project.find(1), :hours => 1, :activity_id => 10, :user => User.find(2), :spent_on => Date.today) - CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value1') - CustomValue.create!(:customized => entry, :custom_field => field, :value => 'value2') - - get :report, :project_id => 1, :columns => 'day', :criteria => ["cf_#{field.id}"] - assert_response :success - end - - def test_report_one_day - 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) - assert_equal "4.25", "%.2f" % assigns(:report).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", :criteria => ["user", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "154.25", "%.2f" % assigns(:report).total_hours - assert_tag :form, - :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} - end - - 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 fields columns - assert_select 'th', :text => 'Database' - assert_select 'th', :text => 'Development status' - assert_select 'th', :text => 'Billable' - - # Custom field row - 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 - get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criteria => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:report) - assert_equal "0.00", "%.2f" % assigns(:report).total_hours - end - - def test_report_status_criterion - get :report, :project_id => 1, :criteria => ['status'] - assert_response :success - assert_template 'report' - assert_tag :tag => 'th', :content => 'Status' - assert_tag :tag => 'td', :content => 'New' - end - - def test_report_all_projects_csv_export - get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :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,User,Activity,2007-3,2007-4,Total time', lines.first - # Total row - 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", "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,User,Activity,2007-3,2007-4,Total time', lines.first - # Total row - assert_equal 'Total time,"","",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", - :criteria => ["user"], :format => "csv" - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - s1 = "\xa5\xce\xa4\xe1,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", - :criteria => ["user"], :format => "csv" - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - s1 = "\xa5\xce\xa4\xe1,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", - :criteria => ["user"], :format => "csv" - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - 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') - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b655ad2d788904a0652e611b77d64e13681af213.svn-base --- a/.svn/pristine/b6/b655ad2d788904a0652e611b77d64e13681af213.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'fileutils' - -module Redmine - module Thumbnail - extend Redmine::Utils::Shell - - CONVERT_BIN = (Redmine::Configuration['imagemagick_convert_command'] || 'convert').freeze - - # Generates a thumbnail for the source image to target - def self.generate(source, target, size) - return nil unless convert_available? - unless File.exists?(target) - directory = File.dirname(target) - unless File.exists?(directory) - FileUtils.mkdir_p directory - end - size_option = "#{size}x#{size}>" - cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -thumbnail #{shell_quote size_option} #{shell_quote target}" - unless system(cmd) - logger.error("Creating thumbnail failed (#{$?}):\nCommand: #{cmd}") - return nil - end - end - target - end - - def self.convert_available? - return @convert_available if defined?(@convert_available) - @convert_available = system("#{shell_quote CONVERT_BIN} -version") rescue false - logger.warn("Imagemagick's convert binary (#{CONVERT_BIN}) not available") unless @convert_available - @convert_available - end - - def self.logger - Rails.logger - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b660ee83289e164ea0413ddff0a433dfd70ad1fb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b660ee83289e164ea0413ddff0a433dfd70ad1fb.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b67ea0ec77b4b276a5e977476ff6fc94a0544b66.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b6/b67ea0ec77b4b276a5e977476ff6fc94a0544b66.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 RoutingActivitiesTest < ActionController::IntegrationTest + def test_activities + assert_routing( + { :method => 'get', :path => "/activity" }, + { :controller => 'activities', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/activity.atom" }, + { :controller => 'activities', :action => 'index', :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/projects/33/activity" }, + { :controller => 'activities', :action => 'index', :id => '33' } + ) + assert_routing( + { :method => 'get', :path => "/projects/33/activity.atom" }, + { :controller => 'activities', :action => 'index', :id => '33', + :format => 'atom' } + ) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b69919be9b30cca58da48f5eb6fb2fab0f0c471f.svn-base --- a/.svn/pristine/b6/b69919be9b30cca58da48f5eb6fb2fab0f0c471f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1164 +0,0 @@ -# Chinese (Taiwan) translations for Ruby on Rails -# by tsechingho (http://github.com/tsechingho) -# See http://github.com/svenfuchs/rails-i18n/ for details. - -"zh-TW": - 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: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月] - abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] - # 使用於 date_select 與 datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y年%b%d日 %A %H:%M:%S %Z" - time: "%H:%M" - short: "%b%d日 %H:%M" - long: "%Y年%b%d日 %H:%M" - am: "AM" - pm: "PM" - -# 使用於 array.to_sentence. - support: - array: - words_connector: ", " - two_words_connector: " 和 " - last_word_connector: ", 和 " - sentence_connector: "且" - skip_last_comma: false - - number: - # 使用於 number_with_delimiter() - # 同時也是 'currency', 'percentage', 'precision', 與 'human' 的預設值 - format: - # 設定小數點分隔字元,以使用更高的準確度 (例如: 1.0 / 2.0 == 0.5) - separator: "." - # 千分位符號 (例如:一百萬是 1,000,000) (均以三個位數來分組) - delimiter: "," - # 小數點分隔字元後之精確位數 (數字 1 搭配 2 位精確位數為: 1.00) - precision: 3 - - # 使用於 number_to_currency() - currency: - format: - # 貨幣符號的位置? %u 是貨幣符號, %n 是數值 (預設值: $5.00) - format: "%u%n" - unit: "NT$" - # 下列三個選項設定, 若有設定值將會取代 number.format 成為預設值 - separator: "." - delimiter: "," - precision: 2 - - # 使用於 number_to_percentage() - percentage: - format: - # 下列三個選項設定, 若有設定值將會取代 number.format 成為預設值 - # separator: - delimiter: "" - # precision: - - # 使用於 number_to_precision() - precision: - format: - # 下列三個選項設定, 若有設定值將會取代 number.format 成為預設值 - # separator: - delimiter: "" - # precision: - - # 使用於 number_to_human_size() - human: - format: - # 下列三個選項設定, 若有設定值將會取代 number.format 成為預設值 - # separator: - delimiter: "" - precision: 3 - # 儲存單位輸出格式. - # %u 是儲存單位, %n 是數值 (預設值: 2 MB) - storage_units: - format: "%n %u" - units: - byte: - one: "位元組 (B)" - other: "位元組 (B)" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - # 使用於 distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - 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 hour" - other: "%{count} hours" - 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} 年" - prompts: - year: "年" - month: "月" - day: "日" - hour: "時" - minute: "分" - second: "秒" - - activerecord: - errors: - template: - header: - one: "有 1 個錯誤發生使得「%{model}」無法被儲存。" - other: "有 %{count} 個錯誤發生使得「%{model}」無法被儲存。" - # The variable :count is also available - body: "下面所列欄位有問題:" - # 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: "沒有包含在列表中" - 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: "必須是偶數" - # Append your own errors here or at the model/attributes scope. - greater_than_start_date: "必須在開始日期之後" - not_same_project: "不屬於同一個專案" - circular_dependency: "這個關聯會導致環狀相依" - cant_link_an_issue_with_a_descendant: "問題無法被連結至自己的子任務" - - # You can define own errors for models or model attributes. - # The values :model, :attribute and :value are always available for interpolation. - # - # For example, - # models: - # user: - # blank: "This is a custom blank message for %{model}: %{attribute}" - # attributes: - # login: - # blank: "This is a custom blank message for User login" - # Will define custom blank validation message for User model and - # custom blank validation message for login attribute of User model. - #models: - - # Translate model names. Used in Model.human_name(). - #models: - # For example, - # user: "Dude" - # will translate User model name to "Dude" - - # Translate model attribute names. Used in Model.human_attribute_name(attribute). - #attributes: - # For example, - # user: - # login: "Handle" - # will translate User attribute "login" as "Handle" - - actionview_instancetag_blank_option: 請選擇 - - general_text_No: '否' - general_text_Yes: '是' - general_text_no: '否' - general_text_yes: '是' - general_lang_name: 'Traditional Chinese (繁體中文)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: Big5 - general_pdf_encoding: Big5 - general_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_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: '請選擇一個來源問題追蹤標籤或角色' - error_workflow_copy_target: '請選擇一個(或多個)目的問題追蹤標籤或角色' - error_unable_delete_issue_status: '無法刪除問題狀態' - error_unable_to_connect: "無法連線至(%{value})" - error_attachment_too_big: "這個檔案無法被上傳,因為它已經超過最大的檔案大小 (%{max_size})" - error_session_expired: "您的工作階段已經過期。請重新登入。" - warning_attachments_not_saved: "%{count} 個附加檔案無法被儲存。" - - mail_subject_lost_password: 您的 Redmine 網站密碼 - mail_body_lost_password: '欲變更您的 Redmine 網站密碼, 請點選以下鏈結:' - mail_subject_register: 啟用您的 Redmine 帳號 - mail_body_register: '欲啟用您的 Redmine 帳號, 請點選以下鏈結:' - mail_body_account_information_external: "您可以使用 %{value} 帳號登入 Redmine 網站。" - mail_body_account_information: 您的 Redmine 帳號資訊 - mail_subject_account_activation_request: Redmine 帳號啟用需求通知 - 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} 更新。" - - 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: Type - field_host: 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: 網址 - 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: 可被看見 - 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: 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: 私人筆記 - - 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 新聞限制 - setting_autofetch_changesets: 自動擷取認可 - setting_default_projects_public: 新建立之專案預設為「公開」 - 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_cross_project_subtasks: 允許跨專案的子任務 - 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: 啟用處理傳入電子郵件的服務 - setting_mail_handler_api_key: API 金鑰 - setting_sequential_project_identifiers: 循序產生專案識別碼 - setting_gravatar_enabled: 啟用 Gravatar 全球認證大頭像 - setting_gravatar_default: 預設全球認證大頭像圖片 - 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: 甘特圖中項目顯示數量的最大值 - 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: "縮圖大小 (單位: 像素 pixels)" - setting_non_working_week_days: 非工作日 - - 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_issues_private: 設定問題為公開或私人 - permission_set_own_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: 儲存查詢 - 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: 管理子任務 - 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: 無專案 - 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: 讀取... - 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_permissions: 權限 - label_current_status: 目前狀態 - label_new_statuses_allowed: 可變更至以下狀態 - label_all: 全部 - label_any: 任意一個 - label_none: 空值 - label_nobody: 無名 - label_next: 下一頁 - label_previous: 上一頁 - label_used_by: 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_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_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: 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: 差異 - 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: 置頂 - 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: 版本控管 - 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: 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: "搜尋用戶:" - 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: "專案是 %{name}" - label_attribute_of_author: "作者是 %{name}" - label_attribute_of_assigned_to: "被指派者是 %{name}" - label_attribute_of_fixed_version: "版本是 %{name}" - label_cross_project_descendants: 與子專案共用 - label_cross_project_tree: 與專案樹共用 - label_cross_project_hierarchy: 與專案階層架構共用 - label_cross_project_system: 與全部的專案共用 - - 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: 選擇欲寄送提醒通知郵件之動作 - 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_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: "您尚未設定電子郵件傳送方式,因此提醒選項已被停用。\n請在 config/configuration.yml 中設定 SMTP 之後,重新啟動 Redmine,以啟用電子郵件提醒選項。" - 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: 縮小 - text_warn_on_leaving_unsaved: "若您離開這個頁面,此頁面所包含的未儲存資料將會遺失。" - text_scm_path_encoding_note: "預設: UTF-8" - text_git_repository_note: 儲存機制是本機的空(bare)目錄 (即: /gitrepo, c:\gitrepo) - text_mercurial_repository_note: 本機儲存機制 (即: /hgrepo, c:\hgrepo) - text_scm_command: 命令 - text_scm_command_version: 版本 - text_scm_config: 您可以在 config/configuration.yml 中設定 SCM 命令。請在編輯該檔案之後重新啟動 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: |- - 您確定要繼續這個動作嗎? - 您的帳戶將會被永久刪除,且無法被重新啟用。 - text_session_expiration_settings: "警告:變更這些設定將會導致包含您在內的所有工作階段過期。" - text_project_closed: 此專案已被關閉,僅供唯讀使用。 - - 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_repository_identifier_info: '僅允許使用小寫英文字母 (a-z), 阿拉伯數字, 虛線與底線。
    一旦儲存之後, 代碼便無法再次被更改。' diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b6/b6af6ff709a2c46123e5e47c3c80bd99fd0c8e17.svn-base --- a/.svn/pristine/b6/b6af6ff709a2c46123e5e47c3c80bd99fd0c8e17.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b7/b74317bccac34414d54322b279285d4bd780b35b.svn-base --- a/.svn/pristine/b7/b74317bccac34414d54322b279285d4bd780b35b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1108 +0,0 @@ -# Hungarian translations for Ruby on Rails -# by Richard Abonyi (richard.abonyi@gmail.com) -# thanks to KKata, replaced and #hup.hu -# Cleaned up by László Bácsi (http://lackac.hu) -# updated by kfl62 kfl62g@gmail.com -# updated by Gábor Takács (taky77@gmail.com) - -"hu": - direction: ltr - date: - formats: - default: "%Y.%m.%d." - short: "%b %e." - long: "%Y. %B %e." - day_names: [vasárnap, hétfő, kedd, szerda, csütörtök, péntek, szombat] - abbr_day_names: [v., h., k., sze., cs., p., szo.] - month_names: [~, január, február, március, április, május, június, július, augusztus, szeptember, október, november, december] - abbr_month_names: [~, jan., febr., márc., ápr., máj., jún., júl., aug., szept., okt., nov., dec.] - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y. %b %d., %H:%M" - time: "%H:%M" - short: "%b %e., %H:%M" - long: "%Y. %B %e., %A, %H:%M" - am: "de." - pm: "du." - - datetime: - distance_in_words: - half_a_minute: 'fél perc' - less_than_x_seconds: -# zero: 'kevesebb, mint 1 másodperce' - one: 'kevesebb, mint 1 másodperce' - other: 'kevesebb, mint %{count} másodperce' - x_seconds: - one: '1 másodperce' - other: '%{count} másodperce' - less_than_x_minutes: -# zero: 'kevesebb, mint 1 perce' - one: 'kevesebb, mint 1 perce' - other: 'kevesebb, mint %{count} perce' - x_minutes: - one: '1 perce' - other: '%{count} perce' - about_x_hours: - one: 'csaknem 1 órája' - other: 'csaknem %{count} órája' - x_hours: - one: "1 óra" - other: "%{count} óra" - x_days: - one: '1 napja' - other: '%{count} napja' - about_x_months: - one: 'csaknem 1 hónapja' - other: 'csaknem %{count} hónapja' - x_months: - one: '1 hónapja' - other: '%{count} hónapja' - about_x_years: - one: 'csaknem 1 éve' - other: 'csaknem %{count} éve' - over_x_years: - one: 'több, mint 1 éve' - other: 'több, mint %{count} éve' - almost_x_years: - one: "csaknem 1 éve" - other: "csaknem %{count} éve" - prompts: - year: "Év" - month: "Hónap" - day: "Nap" - hour: "Óra" - minute: "Perc" - second: "Másodperc" - - number: - format: - precision: 2 - separator: ',' - delimiter: ' ' - currency: - format: - unit: 'Ft' - precision: 0 - format: '%n %u' - separator: "," - delimiter: "" - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "bájt" - other: "bájt" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: -# sentence_connector: "és" -# skip_last_comma: true - words_connector: ", " - two_words_connector: " és " - last_word_connector: " és " - activerecord: - errors: - template: - header: - one: "1 hiba miatt nem menthető a következő: %{model}" - other: "%{count} hiba miatt nem menthető a következő: %{model}" - body: "Problémás mezők:" - messages: - inclusion: "nincs a listában" - exclusion: "nem elérhető" - invalid: "nem megfelelő" - confirmation: "nem egyezik" - accepted: "nincs elfogadva" - empty: "nincs megadva" - blank: "nincs megadva" - too_long: "túl hosszú (nem lehet több %{count} karakternél)" - too_short: "túl rövid (legalább %{count} karakter kell legyen)" - wrong_length: "nem megfelelő hosszúságú (%{count} karakter szükséges)" - taken: "már foglalt" - not_a_number: "nem szám" - greater_than: "nagyobb kell legyen, mint %{count}" - greater_than_or_equal_to: "legalább %{count} kell legyen" - equal_to: "pontosan %{count} kell legyen" - less_than: "kevesebb, mint %{count} kell legyen" - less_than_or_equal_to: "legfeljebb %{count} lehet" - odd: "páratlan kell legyen" - even: "páros kell legyen" - greater_than_start_date: "nagyobbnak kell lennie, mint az indítás dátuma" - not_same_project: "nem azonos projekthez tartozik" - circular_dependency: "Ez a kapcsolat egy körkörös függőséget eredményez" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Kérem válasszon - - general_text_No: 'Nem' - general_text_Yes: 'Igen' - general_text_no: 'nem' - general_text_yes: 'igen' - general_lang_name: 'Magyar' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-2 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: A fiók adatai sikeresen frissítve. - notice_account_invalid_creditentials: Hibás felhasználói név, vagy jelszó - notice_account_password_updated: A jelszó módosítása megtörtént. - notice_account_wrong_password: Hibás jelszó - notice_account_register_done: A fiók sikeresen létrehozva. Aktiválásához kattints az e-mailben kapott linkre - notice_account_unknown_email: Ismeretlen felhasználó. - notice_can_t_change_password: A fiók külső azonosítási forrást használ. A jelszó megváltoztatása nem lehetséges. - notice_account_lost_email_sent: Egy e-mail üzenetben postáztunk Önnek egy leírást az új jelszó beállításáról. - notice_account_activated: Fiókját aktiváltuk. Most már be tud jelentkezni a rendszerbe. - notice_successful_create: Sikeres létrehozás. - notice_successful_update: Sikeres módosítás. - notice_successful_delete: Sikeres törlés. - notice_successful_connection: Sikeres bejelentkezés. - notice_file_not_found: Az oldal, amit meg szeretne nézni nem található, vagy átkerült egy másik helyre. - notice_locking_conflict: Az adatot egy másik felhasználó idő közben módosította. - notice_not_authorized: Nincs hozzáférési engedélye ehhez az oldalhoz. - notice_email_sent: "Egy e-mail üzenetet küldtünk a következő címre %{value}" - notice_email_error: "Hiba történt a levél küldése közben (%{value})" - notice_feeds_access_key_reseted: Az RSS hozzáférési kulcsát újra generáltuk. - notice_failed_to_save_issues: "Nem sikerült a %{count} feladat(ok) mentése a %{total} -ban kiválasztva: %{ids}." - notice_no_issue_selected: "Nincs feladat kiválasztva! Kérem jelölje meg melyik feladatot szeretné szerkeszteni!" - notice_account_pending: "A fiókja létrejött, és adminisztrátori jóváhagyásra vár." - notice_default_data_loaded: Az alapértelmezett konfiguráció betöltése sikeresen megtörtént. - - error_can_t_load_default_data: "Az alapértelmezett konfiguráció betöltése nem lehetséges: %{value}" - error_scm_not_found: "A bejegyzés, vagy revízió nem található a tárolóban." - error_scm_command_failed: "A tároló elérése közben hiba lépett fel: %{value}" - error_scm_annotate: "A bejegyzés nem létezik, vagy nics jegyzetekkel ellátva." - error_issue_not_found_in_project: 'A feladat nem található, vagy nem ehhez a projekthez tartozik' - - mail_subject_lost_password: Az Ön Redmine jelszava - mail_body_lost_password: 'A Redmine jelszó megváltoztatásához, kattintson a következő linkre:' - mail_subject_register: Redmine azonosító aktiválása - mail_body_register: 'A Redmine azonosítója aktiválásához, kattintson a következő linkre:' - mail_body_account_information_external: "A %{value} azonosító használatával bejelentkezhet a Redmine-ba." - mail_body_account_information: Az Ön Redmine azonosítójának információi - mail_subject_account_activation_request: Redmine azonosító aktiválási kérelem - mail_body_account_activation_request: "Egy új felhasználó (%{value}) regisztrált, azonosítója jóváhasgyásra várakozik:" - - - field_name: Név - field_description: Leírás - field_summary: Összegzés - field_is_required: Kötelező - field_firstname: Keresztnév - field_lastname: Vezetéknév - field_mail: E-mail - field_filename: Fájl - field_filesize: Méret - field_downloads: Letöltések - field_author: Szerző - field_created_on: Létrehozva - field_updated_on: Módosítva - field_field_format: Formátum - field_is_for_all: Minden projekthez - field_possible_values: Lehetséges értékek - field_regexp: Reguláris kifejezés - field_min_length: Minimum hossz - field_max_length: Maximum hossz - field_value: Érték - field_category: Kategória - field_title: Cím - field_project: Projekt - field_issue: Feladat - field_status: Státusz - field_notes: Feljegyzések - field_is_closed: Feladat lezárva - field_is_default: Alapértelmezett érték - field_tracker: Típus - field_subject: Tárgy - field_due_date: Befejezés dátuma - field_assigned_to: Felelős - field_priority: Prioritás - field_fixed_version: Cél verzió - field_user: Felhasználó - field_role: Szerepkör - field_homepage: Weboldal - field_is_public: Nyilvános - field_parent: Szülő projekt - field_is_in_roadmap: Feladatok látszanak az életútban - field_login: Azonosító - field_mail_notification: E-mail értesítések - field_admin: Adminisztrátor - field_last_login_on: Utolsó bejelentkezés - field_language: Nyelv - field_effective_date: Dátum - field_password: Jelszó - field_new_password: Új jelszó - field_password_confirmation: Megerősítés - field_version: Verzió - field_type: Típus - field_host: Kiszolgáló - field_port: Port - field_account: Felhasználói fiók - field_base_dn: Base DN - field_attr_login: Bejelentkezési tulajdonság - field_attr_firstname: Keresztnév - field_attr_lastname: Vezetéknév - field_attr_mail: E-mail - field_onthefly: On-the-fly felhasználó létrehozás - field_start_date: Kezdés dátuma - field_done_ratio: Készültség (%) - field_auth_source: Azonosítási mód - field_hide_mail: Rejtse el az e-mail címem - field_comments: Megjegyzés - field_url: URL - field_start_page: Kezdőlap - field_subproject: Alprojekt - field_hours: Óra - field_activity: Aktivitás - field_spent_on: Dátum - field_identifier: Azonosító - field_is_filter: Szűrőként használható - field_issue_to: Kapcsolódó feladat - field_delay: Késés - field_assignable: Feladat rendelhető ehhez a szerepkörhöz - field_redirect_existing_links: Létező linkek átirányítása - field_estimated_hours: Becsült időigény - field_column_names: Oszlopok - field_time_zone: Időzóna - field_searchable: Kereshető - field_default_value: Alapértelmezett érték - field_comments_sorting: Feljegyzések megjelenítése - - setting_app_title: Alkalmazás címe - setting_app_subtitle: Alkalmazás alcíme - setting_welcome_text: Üdvözlő üzenet - setting_default_language: Alapértelmezett nyelv - setting_login_required: Azonosítás szükséges - setting_self_registration: Regisztráció - setting_attachment_max_size: Melléklet max. mérete - setting_issues_export_limit: Feladatok exportálásának korlátja - setting_mail_from: Kibocsátó e-mail címe - setting_bcc_recipients: Titkos másolat címzet (bcc) - setting_host_name: Kiszolgáló neve - setting_text_formatting: Szöveg formázás - setting_wiki_compression: Wiki történet tömörítés - setting_feeds_limit: RSS tartalom korlát - setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak - setting_autofetch_changesets: Commitok automatikus lehúzása - setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez - setting_commit_ref_keywords: Hivatkozó kulcsszavak - setting_commit_fix_keywords: Javítások kulcsszavai - setting_autologin: Automatikus bejelentkezés - setting_date_format: Dátum formátum - setting_time_format: Idő formátum - setting_cross_project_issue_relations: Kereszt-projekt feladat hivatkozások engedélyezése - setting_issue_list_default_columns: Az alapértelmezésként megjelenített oszlopok a feladat listában - setting_emails_footer: E-mail lábléc - setting_protocol: Protokol - setting_per_page_options: Objektum / oldal opciók - setting_user_format: Felhasználók megjelenítésének formája - setting_activity_days_default: Napok megjelenítése a project aktivitásnál - setting_display_subprojects_issues: Alapértelmezettként mutassa az alprojektek feladatait is a projekteken - setting_start_of_week: A hét első napja - - project_module_issue_tracking: Feladat követés - project_module_time_tracking: Idő rögzítés - project_module_news: Hírek - project_module_documents: Dokumentumok - project_module_files: Fájlok - project_module_wiki: Wiki - project_module_repository: Forráskód - project_module_boards: Fórumok - - label_user: Felhasználó - label_user_plural: Felhasználók - label_user_new: Új felhasználó - label_project: Projekt - label_project_new: Új projekt - label_project_plural: Projektek - label_x_projects: - zero: nincsenek projektek - one: 1 projekt - other: "%{count} projekt" - label_project_all: Az összes projekt - label_project_latest: Legutóbbi projektek - label_issue: Feladat - label_issue_new: Új feladat - label_issue_plural: Feladatok - label_issue_view_all: Minden feladat - label_issues_by: "%{value} feladatai" - label_issue_added: Feladat hozzáadva - label_issue_updated: Feladat frissítve - label_document: Dokumentum - label_document_new: Új dokumentum - label_document_plural: Dokumentumok - label_document_added: Dokumentum hozzáadva - label_role: Szerepkör - label_role_plural: Szerepkörök - label_role_new: Új szerepkör - label_role_and_permissions: Szerepkörök, és jogosultságok - label_member: Résztvevő - label_member_new: Új résztvevő - label_member_plural: Résztvevők - label_tracker: Feladat típus - label_tracker_plural: Feladat típusok - label_tracker_new: Új feladat típus - label_workflow: Workflow - label_issue_status: Feladat státusz - label_issue_status_plural: Feladat státuszok - label_issue_status_new: Új státusz - label_issue_category: Feladat kategória - label_issue_category_plural: Feladat kategóriák - label_issue_category_new: Új kategória - label_custom_field: Egyéni mező - label_custom_field_plural: Egyéni mezők - label_custom_field_new: Új egyéni mező - label_enumerations: Felsorolások - label_enumeration_new: Új érték - label_information: Információ - label_information_plural: Információk - label_please_login: Jelentkezzen be - label_register: Regisztráljon - label_password_lost: Elfelejtett jelszó - label_home: Kezdőlap - label_my_page: Saját kezdőlapom - label_my_account: Fiókom adatai - label_my_projects: Saját projektem - label_administration: Adminisztráció - label_login: Bejelentkezés - label_logout: Kijelentkezés - label_help: Súgó - label_reported_issues: Bejelentett feladatok - label_assigned_to_me_issues: A nekem kiosztott feladatok - label_last_login: Utolsó bejelentkezés - label_registered_on: Regisztrált - label_activity: Történések - label_overall_activity: Teljes aktivitás - label_new: Új - label_logged_as: Bejelentkezve, mint - label_environment: Környezet - label_authentication: Azonosítás - label_auth_source: Azonosítás módja - label_auth_source_new: Új azonosítási mód - label_auth_source_plural: Azonosítási módok - label_subproject_plural: Alprojektek - label_and_its_subprojects: "%{value} és alprojektjei" - label_min_max_length: Min - Max hossz - label_list: Lista - label_date: Dátum - label_integer: Egész - label_float: Lebegőpontos - label_boolean: Logikai - label_string: Szöveg - label_text: Hosszú szöveg - label_attribute: Tulajdonság - label_attribute_plural: Tulajdonságok - label_no_data: Nincs megjeleníthető adat - label_change_status: Státusz módosítása - label_history: Történet - label_attachment: Fájl - label_attachment_new: Új fájl - label_attachment_delete: Fájl törlése - label_attachment_plural: Fájlok - label_file_added: Fájl hozzáadva - label_report: Jelentés - label_report_plural: Jelentések - label_news: Hírek - label_news_new: Hír hozzáadása - label_news_plural: Hírek - label_news_latest: Legutóbbi hírek - label_news_view_all: Minden hír megtekintése - label_news_added: Hír hozzáadva - label_settings: Beállítások - label_overview: Áttekintés - label_version: Verzió - label_version_new: Új verzió - label_version_plural: Verziók - label_confirmation: Jóváhagyás - label_export_to: Exportálás - label_read: Olvas... - label_public_projects: Nyilvános projektek - label_open_issues: nyitott - label_open_issues_plural: nyitott - label_closed_issues: lezárt - label_closed_issues_plural: lezárt - label_x_open_issues_abbr_on_total: - zero: nyitott 0 / %{total} - one: nyitott 1 / %{total} - other: "nyitott %{count} / %{total}" - label_x_open_issues_abbr: - zero: 0 nyitott - one: 1 nyitott - other: "%{count} nyitott" - label_x_closed_issues_abbr: - zero: 0 lezárt - one: 1 lezárt - other: "%{count} lezárt" - label_total: Összesen - label_permissions: Jogosultságok - label_current_status: Jelenlegi státusz - label_new_statuses_allowed: Státusz változtatások engedélyei - label_all: mind - label_none: nincs - label_nobody: senki - label_next: Következő - label_previous: Előző - label_used_by: Használja - label_details: Részletek - label_add_note: Jegyzet hozzáadása - label_per_page: Oldalanként - label_calendar: Naptár - label_months_from: hónap, kezdve - label_gantt: Gantt - label_internal: Belső - label_last_changes: "utolsó %{count} változás" - label_change_view_all: Minden változás megtekintése - label_personalize_page: Az oldal testreszabása - label_comment: Megjegyzés - label_comment_plural: Megjegyzés - label_x_comments: - zero: nincs megjegyzés - one: 1 megjegyzés - other: "%{count} megjegyzés" - label_comment_add: Megjegyzés hozzáadása - label_comment_added: Megjegyzés hozzáadva - label_comment_delete: Megjegyzések törlése - label_query: Egyéni lekérdezés - label_query_plural: Egyéni lekérdezések - label_query_new: Új lekérdezés - label_filter_add: Szűrő hozzáadása - label_filter_plural: Szűrők - label_equals: egyenlő - label_not_equals: nem egyenlő - label_in_less_than: kevesebb, mint - label_in_more_than: több, mint - label_in: in - label_today: ma - label_all_time: mindenkor - label_yesterday: tegnap - label_this_week: aktuális hét - label_last_week: múlt hét - label_last_n_days: "az elmúlt %{count} nap" - label_this_month: aktuális hónap - label_last_month: múlt hónap - label_this_year: aktuális év - label_date_range: Dátum intervallum - label_less_than_ago: kevesebb, mint nappal ezelőtt - label_more_than_ago: több, mint nappal ezelőtt - label_ago: nappal ezelőtt - label_contains: tartalmazza - label_not_contains: nem tartalmazza - label_day_plural: nap - label_repository: Forráskód - label_repository_plural: Forráskódok - label_browse: Tallóz - label_revision: Revízió - label_revision_plural: Revíziók - label_associated_revisions: Kapcsolt revíziók - label_added: hozzáadva - label_modified: módosítva - label_deleted: törölve - label_latest_revision: Legutolsó revízió - label_latest_revision_plural: Legutolsó revíziók - label_view_revisions: Revíziók megtekintése - label_max_size: Maximális méret - label_sort_highest: Az elejére - label_sort_higher: Eggyel feljebb - label_sort_lower: Eggyel lejjebb - label_sort_lowest: Az aljára - label_roadmap: Életút - label_roadmap_due_in: "Elkészültéig várhatóan még %{value}" - label_roadmap_overdue: "%{value} késésben" - label_roadmap_no_issues: Nincsenek feladatok ehhez a verzióhoz - label_search: Keresés - label_result_plural: Találatok - label_all_words: Minden szó - label_wiki: Wiki - label_wiki_edit: Wiki szerkesztés - label_wiki_edit_plural: Wiki szerkesztések - label_wiki_page: Wiki oldal - label_wiki_page_plural: Wiki oldalak - label_index_by_title: Cím szerint indexelve - label_index_by_date: Dátum szerint indexelve - label_current_version: Jelenlegi verzió - label_preview: Előnézet - label_feed_plural: Visszajelzések - label_changes_details: Változások részletei - label_issue_tracking: Feladat követés - label_spent_time: Ráfordított idő - label_f_hour: "%{value} óra" - label_f_hour_plural: "%{value} óra" - label_time_tracking: Idő rögzítés - label_change_plural: Változások - label_statistics: Statisztikák - label_commits_per_month: Commitok havonta - label_commits_per_author: Commitok szerzőnként - label_view_diff: Különbségek megtekintése - label_diff_inline: soronként - label_diff_side_by_side: egymás mellett - label_options: Opciók - label_copy_workflow_from: Workflow másolása innen - label_permissions_report: Jogosultsági riport - label_watched_issues: Megfigyelt feladatok - label_related_issues: Kapcsolódó feladatok - label_applied_status: Alkalmazandó státusz - label_loading: Betöltés... - label_relation_new: Új kapcsolat - label_relation_delete: Kapcsolat törlése - label_relates_to: kapcsolódik - label_duplicates: duplikálja - label_blocks: zárolja - label_blocked_by: zárolta - label_precedes: megelőzi - label_follows: követi - label_end_to_start: végétől indulásig - label_end_to_end: végétől végéig - label_start_to_start: indulástól indulásig - label_start_to_end: indulástól végéig - label_stay_logged_in: Emlékezzen rám - label_disabled: kikapcsolva - label_show_completed_versions: A kész verziók mutatása - label_me: én - label_board: Fórum - label_board_new: Új fórum - label_board_plural: Fórumok - label_topic_plural: Témák - label_message_plural: Üzenetek - label_message_last: Utolsó üzenet - label_message_new: Új üzenet - label_message_posted: Üzenet hozzáadva - label_reply_plural: Válaszok - label_send_information: Fiók infomációk küldése a felhasználónak - label_year: Év - label_month: Hónap - label_week: Hét - label_date_from: 'Kezdet:' - label_date_to: 'Vége:' - label_language_based: A felhasználó nyelve alapján - label_sort_by: "%{value} szerint rendezve" - label_send_test_email: Teszt e-mail küldése - label_feeds_access_key_created_on: "RSS hozzáférési kulcs létrehozva %{value}" - label_module_plural: Modulok - label_added_time_by: "%{author} adta hozzá %{age}" - label_updated_time: "Utolsó módosítás %{value}" - label_jump_to_a_project: Ugrás projekthez... - label_file_plural: Fájlok - label_changeset_plural: Changesets - label_default_columns: Alapértelmezett oszlopok - label_no_change_option: (Nincs változás) - label_bulk_edit_selected_issues: A kiválasztott feladatok kötegelt szerkesztése - label_theme: Téma - label_default: Alapértelmezett - label_search_titles_only: Keresés csak a címekben - label_user_mail_option_all: "Minden eseményről minden saját projektemben" - label_user_mail_option_selected: "Minden eseményről a kiválasztott projektekben..." - label_user_mail_no_self_notified: "Nem kérek értesítést az általam végzett módosításokról" - label_registration_activation_by_email: Fiók aktiválása e-mailben - label_registration_manual_activation: Manuális fiók aktiválás - label_registration_automatic_activation: Automatikus fiók aktiválás - label_display_per_page: "Oldalanként: %{value}" - label_age: Kor - label_change_properties: Tulajdonságok változtatása - label_general: Általános - label_more: továbbiak - label_scm: SCM - label_plugins: Pluginek - label_ldap_authentication: LDAP azonosítás - label_downloads_abbr: D/L - label_optional_description: Opcionális leírás - label_add_another_file: Újabb fájl hozzáadása - label_preferences: Tulajdonságok - label_chronological_order: Időrendben - label_reverse_chronological_order: Fordított időrendben - label_planning: Tervezés - - button_login: Bejelentkezés - button_submit: Elfogad - button_save: Mentés - button_check_all: Mindent kijelöl - button_uncheck_all: Kijelölés törlése - button_delete: Töröl - button_create: Létrehoz - button_test: Teszt - button_edit: Szerkeszt - button_add: Hozzáad - button_change: Változtat - button_apply: Alkalmaz - button_clear: Töröl - button_lock: Zárol - button_unlock: Felold - button_download: Letöltés - button_list: Lista - button_view: Megnéz - button_move: Mozgat - button_back: Vissza - button_cancel: Mégse - button_activate: Aktivál - button_sort: Rendezés - button_log_time: Idő rögzítés - button_rollback: Visszaáll erre a verzióra - button_watch: Megfigyel - button_unwatch: Megfigyelés törlése - button_reply: Válasz - button_archive: Archivál - button_unarchive: Dearchivál - button_reset: Reset - button_rename: Átnevez - button_change_password: Jelszó megváltoztatása - button_copy: Másol - button_annotate: Jegyzetel - button_update: Módosít - button_configure: Konfigurál - - status_active: aktív - status_registered: regisztrált - status_locked: zárolt - - text_select_mail_notifications: Válasszon eseményeket, amelyekről e-mail értesítést kell küldeni. - text_regexp_info: pl. ^[A-Z0-9]+$ - text_min_max_length_info: 0 = nincs korlátozás - text_project_destroy_confirmation: Biztosan törölni szeretné a projektet és vele együtt minden kapcsolódó adatot ? - text_subprojects_destroy_warning: "Az alprojekt(ek): %{value} szintén törlésre kerülnek." - text_workflow_edit: Válasszon egy szerepkört, és egy feladat típust a workflow szerkesztéséhez - text_are_you_sure: Biztos benne ? - text_tip_issue_begin_day: a feladat ezen a napon kezdődik - text_tip_issue_end_day: a feladat ezen a napon ér véget - text_tip_issue_begin_end_day: a feladat ezen a napon kezdődik és ér véget - text_caracters_maximum: "maximum %{count} karakter." - text_caracters_minimum: "Legkevesebb %{count} karakter hosszúnek kell lennie." - text_length_between: "Legalább %{min} és legfeljebb %{max} hosszú karakter." - text_tracker_no_workflow: Nincs workflow definiálva ehhez a feladat típushoz - text_unallowed_characters: Tiltott karakterek - text_comma_separated: Több érték megengedett (vesszővel elválasztva) - text_issues_ref_in_commit_messages: Hivatkozás feladatokra, feladatok javítása a commit üzenetekben - text_issue_added: "%{author} új feladatot hozott létre %{id} sorszámmal." - text_issue_updated: "%{author} módosította a %{id} sorszámú feladatot." - text_wiki_destroy_confirmation: Biztosan törölni szeretné ezt a wiki-t minden tartalmával együtt ? - text_issue_category_destroy_question: "Néhány feladat (%{count}) hozzá van rendelve ehhez a kategóriához. Mit szeretne tenni?" - text_issue_category_destroy_assignments: Kategória hozzárendelés megszüntetése - text_issue_category_reassign_to: Feladatok újra hozzárendelése másik kategóriához - text_user_mail_option: "A nem kiválasztott projektekről csak akkor kap értesítést, ha figyelést kér rá, vagy részt vesz benne (pl. Ön a létrehozó, vagy a hozzárendelő)" - text_no_configuration_data: "Szerepkörök, feladat típusok, feladat státuszok, és workflow adatok még nincsenek konfigurálva.\nErősen ajánlott, az alapértelmezett konfiguráció betöltése, és utána módosíthatja azt." - text_load_default_configuration: Alapértelmezett konfiguráció betöltése - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Biztos benne, hogy törölni szeretné a kijelölt feladato(ka)t ?' - text_select_project_modules: 'Válassza ki az engedélyezett modulokat ehhez a projekthez:' - text_default_administrator_account_changed: Alapértelmezett adminisztrátor fiók megváltoztatva - text_file_repository_writable: Fájl tároló írható - text_rmagick_available: RMagick elérhető (nem kötelező) - text_destroy_time_entries_question: "%{hours} órányi munka van rögzítve a feladatokon, amiket törölni szeretne. Mit szeretne tenni?" - text_destroy_time_entries: A rögzített órák törlése - text_assign_time_entries_to_project: A rögzített órák hozzárendelése a projekthez - text_reassign_time_entries: 'A rögzített órák újra hozzárendelése másik feladathoz:' - - default_role_manager: Vezető - default_role_developer: Fejlesztő - default_role_reporter: Bejelentő - default_tracker_bug: Hiba - default_tracker_feature: Fejlesztés - default_tracker_support: Támogatás - default_issue_status_new: Új - default_issue_status_in_progress: Folyamatban - default_issue_status_resolved: Megoldva - default_issue_status_feedback: Visszajelzés - default_issue_status_closed: Lezárt - default_issue_status_rejected: Elutasított - default_doc_category_user: Felhasználói dokumentáció - default_doc_category_tech: Technikai dokumentáció - default_priority_low: Alacsony - default_priority_normal: Normál - default_priority_high: Magas - default_priority_urgent: Sürgős - default_priority_immediate: Azonnal - default_activity_design: Tervezés - default_activity_development: Fejlesztés - - enumeration_issue_priorities: Feladat prioritások - enumeration_doc_categories: Dokumentum kategóriák - enumeration_activities: Tevékenységek (idő rögzítés) - mail_body_reminder: "%{count} neked kiosztott feladat határidős az elkövetkező %{days} napban:" - mail_subject_reminder: "%{count} feladat határidős az elkövetkező %{days} napban" - text_user_wrote: "%{value} írta:" - label_duplicated_by: duplikálta - setting_enabled_scm: Forráskódkezelő (SCM) engedélyezése - text_enumeration_category_reassign_to: 'Újra hozzárendelés ehhez:' - text_enumeration_destroy_question: "%{count} objektum van hozzárendelve ehhez az értékhez." - label_incoming_emails: Beérkezett levelek - label_generate_key: Kulcs generálása - setting_mail_handler_api_enabled: Web Service engedélyezése a beérkezett levelekhez - setting_mail_handler_api_key: API kulcs - text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÁllítsd be az SMTP szervert a config/configuration.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen." - field_parent_title: Szülő oldal - label_issue_watchers: Megfigyelők - button_quote: Hozzászólás / Idézet - setting_sequential_project_identifiers: Szekvenciális projekt azonosítók generálása - notice_unable_delete_version: A verziót nem lehet törölni - label_renamed: átnevezve - label_copied: lemásolva - setting_plain_text_mail: csak szöveg (nem HTML) - permission_view_files: Fájlok megtekintése - permission_edit_issues: Feladatok szerkesztése - permission_edit_own_time_entries: Saját időnapló szerkesztése - permission_manage_public_queries: Nyilvános kérések kezelése - permission_add_issues: Feladat felvétele - permission_log_time: Idő rögzítése - permission_view_changesets: Változáskötegek megtekintése - permission_view_time_entries: Időrögzítések megtekintése - permission_manage_versions: Verziók kezelése - permission_manage_wiki: Wiki kezelése - permission_manage_categories: Feladat kategóriák kezelése - permission_protect_wiki_pages: Wiki oldalak védelme - permission_comment_news: Hírek kommentelése - permission_delete_messages: Üzenetek törlése - permission_select_project_modules: Projekt modulok kezelése - permission_edit_wiki_pages: Wiki oldalak szerkesztése - permission_add_issue_watchers: Megfigyelők felvétele - permission_view_gantt: Gannt diagramm megtekintése - permission_move_issues: Feladatok mozgatása - permission_manage_issue_relations: Feladat kapcsolatok kezelése - permission_delete_wiki_pages: Wiki oldalak törlése - permission_manage_boards: Fórumok kezelése - permission_delete_wiki_pages_attachments: Csatolmányok törlése - permission_view_wiki_edits: Wiki történet megtekintése - permission_add_messages: Üzenet beküldése - permission_view_messages: Üzenetek megtekintése - permission_manage_files: Fájlok kezelése - permission_edit_issue_notes: Jegyzetek szerkesztése - permission_manage_news: Hírek kezelése - permission_view_calendar: Naptár megtekintése - permission_manage_members: Tagok kezelése - permission_edit_messages: Üzenetek szerkesztése - permission_delete_issues: Feladatok törlése - permission_view_issue_watchers: Megfigyelők listázása - permission_manage_repository: Tárolók kezelése - permission_commit_access: Commit hozzáférés - permission_browse_repository: Tároló böngészése - permission_view_documents: Dokumetumok megtekintése - permission_edit_project: Projekt szerkesztése - permission_add_issue_notes: Jegyzet rögzítése - permission_save_queries: Kérések mentése - permission_view_wiki_pages: Wiki megtekintése - permission_rename_wiki_pages: Wiki oldalak átnevezése - permission_edit_time_entries: Időnaplók szerkesztése - permission_edit_own_issue_notes: Saját jegyzetek szerkesztése - setting_gravatar_enabled: Felhasználói fényképek engedélyezése - label_example: Példa - text_repository_usernames_mapping: "Állítsd be a felhasználó összerendeléseket a Redmine, és a tároló logban található felhasználók között.\nAz azonos felhasználó nevek összerendelése automatikusan megtörténik." - permission_edit_own_messages: Saját üzenetek szerkesztése - permission_delete_own_messages: Saját üzenetek törlése - label_user_activity: "%{value} tevékenységei" - label_updated_time_by: "Módosította %{author} %{age}" - text_diff_truncated: '... A diff fájl vége nem jelenik meg, mert hosszab, mint a megjeleníthető sorok száma.' - setting_diff_max_lines_displayed: A megjelenítendő sorok száma (maximum) a diff fájloknál - text_plugin_assets_writable: Plugin eszközök könyvtár írható - warning_attachments_not_saved: "%{count} fájl mentése nem sikerült." - button_create_and_continue: Létrehozás és folytatás - text_custom_field_possible_values_info: 'Értékenként egy sor' - label_display: Megmutat - field_editable: Szerkeszthető - setting_repository_log_display_limit: Maximum hány revíziót mutasson meg a log megjelenítésekor - setting_file_max_size_displayed: Maximum mekkora szövegfájlokat jelenítsen meg soronkénti összehasonlításnál - field_watcher: Megfigyelő - setting_openid: OpenID regisztráció és bejelentkezés engedélyezése - field_identity_url: OpenID URL - label_login_with_open_id_option: bejelentkezés OpenID használatával - field_content: Tartalom - label_descending: Csökkenő - label_sort: Rendezés - label_ascending: Növekvő - label_date_from_to: "%{start} -tól %{end} -ig" - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - text_wiki_page_destroy_question: Ennek az oldalnak %{descendants} gyermek-, és leszármazott oldala van. Mit szeretne tenni? - text_wiki_page_reassign_children: Aloldalak hozzárendelése ehhez a szülő oldalhoz - text_wiki_page_nullify_children: Aloldalak átalakítása főoldallá - text_wiki_page_destroy_children: Minden aloldal és leszármazottjának törlése - setting_password_min_length: Minimum jelszó hosszúság - field_group_by: Szerint csoportosítva - mail_subject_wiki_content_updated: "'%{id}' wiki oldal frissítve" - label_wiki_content_added: Wiki oldal hozzáadva - mail_subject_wiki_content_added: "Új wiki oldal: '%{id}'" - mail_body_wiki_content_added: "%{author} létrehozta a '%{id}' wiki oldalt." - label_wiki_content_updated: Wiki oldal frissítve - mail_body_wiki_content_updated: "%{author} frissítette a '%{id}' wiki oldalt." - permission_add_project: Projekt létrehozása - setting_new_project_user_role_id: Projekt létrehozási jog nem adminisztrátor felhasználóknak - label_view_all_revisions: Összes verzió - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Nincs feladat típus hozzárendelve ehhez a projekthez. Kérem ellenőrizze a projekt beállításait. - error_no_default_issue_status: Nincs alapértelmezett feladat státusz beállítva. Kérem ellenőrizze a beállításokat (Itt találja "Adminisztráció -> Feladat státuszok"). - text_journal_changed: "%{label} megváltozott, %{old} helyett %{new} lett" - text_journal_set_to: "%{label} új értéke: %{value}" - text_journal_deleted: "%{label} törölve lett (%{old})" - label_group_plural: Csoportok - label_group: Csoport - label_group_new: Új csoport - label_time_entry_plural: Időráfordítás - text_journal_added: "%{label} %{value} hozzáadva" - field_active: Aktív - enumeration_system_activity: Rendszertevékenység - permission_delete_issue_watchers: Megfigyelők törlése - version_status_closed: lezárt - version_status_locked: zárolt - version_status_open: nyitott - error_can_not_reopen_issue_on_closed_version: Lezárt verzióhoz rendelt feladatot nem lehet újranyitni - label_user_anonymous: Névtelen - button_move_and_follow: Mozgatás és követés - setting_default_projects_modules: Alapértelmezett modulok az új projektekhez - setting_gravatar_default: Alapértelmezett Gravatar kép - field_sharing: Megosztás - label_version_sharing_hierarchy: Projekt hierarchiával - label_version_sharing_system: Minden projekttel - label_version_sharing_descendants: Alprojektekkel - label_version_sharing_tree: Projekt fával - label_version_sharing_none: Nincs megosztva - error_can_not_archive_project: A projektet nem lehet archiválni - button_duplicate: Duplikálás - button_copy_and_follow: Másolás és követés - label_copy_source: Forrás - setting_issue_done_ratio: Feladat készültségi szint számolása a következő alapján - setting_issue_done_ratio_issue_status: Feladat státusz alapján - error_issue_done_ratios_not_updated: A feladat készültségi szintek nem lettek frissítve. - error_workflow_copy_target: Kérem válasszon cél feladat típus(oka)t és szerepkör(öke)t. - setting_issue_done_ratio_issue_field: A feladat mező alapján - label_copy_same_as_target: A céllal egyező - label_copy_target: Cél - notice_issue_done_ratios_updated: Feladat készültségi szintek frissítve. - error_workflow_copy_source: Kérem válasszon forrás feladat típust vagy szerepkört - label_update_issue_done_ratios: Feladat készültségi szintek frissítése - setting_start_of_week: A hét első napja - permission_view_issues: Feladatok megtekintése - label_display_used_statuses_only: Csak olyan feladat státuszok megjelenítése, amit ez a feladat típus használ - label_revision_id: Revízió %{value} - label_api_access_key: API hozzáférési kulcs - label_api_access_key_created_on: API hozzáférési kulcs létrehozva %{value} ezelőtt - label_feeds_access_key: RSS hozzáférési kulcs - notice_api_access_key_reseted: Az API hozzáférési kulcsa újragenerálva. - setting_rest_api_enabled: REST web service engedélyezése - label_missing_api_access_key: Egy API hozzáférési kulcs hiányzik - label_missing_feeds_access_key: RSS hozzáférési kulcs hiányzik - button_show: Megmutat - text_line_separated: Több érték megadása lehetséges (soronként 1 érték). - setting_mail_handler_body_delimiters: E-mailek levágása a következő sorok valamelyike esetén - permission_add_subprojects: Alprojektek létrehozása - label_subproject_new: Új alprojekt - text_own_membership_delete_confirmation: |- - Arra készül, hogy eltávolítja egyes vagy minden jogosultságát! Ezt követően lehetséges, hogy nem fogja tudni szerkeszteni ezt a projektet! - Biztosan folyatni szeretné? - label_close_versions: Kész verziók lezárása - label_board_sticky: Sticky - setting_cache_formatted_text: Formázott szöveg gyorsítótárazása (Cache) - permission_export_wiki_pages: Wiki oldalak exportálása - permission_manage_project_activities: Projekt tevékenységek kezelése - label_board_locked: Zárolt - error_can_not_delete_custom_field: Nem lehet törölni az egyéni mezőt - permission_manage_subtasks: Alfeladatok kezelése - label_profile: Profil - error_unable_to_connect: Nem lehet csatlakozni (%{value}) - error_can_not_remove_role: Ez a szerepkör használatban van és ezért nem törölhető- - field_parent_issue: Szülő feladat - error_unable_delete_issue_status: Nem lehet törölni a feladat állapotát - label_subtask_plural: Alfeladatok - error_can_not_delete_tracker: Ebbe a kategóriába feladatok tartoznak és ezért nem törölhető. - label_project_copy_notifications: Küldjön e-mail értesítéseket projektmásolás közben. - field_principal: Felelős - label_my_page_block: Saját kezdőlap-blokk - notice_failed_to_save_members: "Nem sikerült menteni a tago(ka)t: %{errors}." - text_zoom_out: Kicsinyít - text_zoom_in: Nagyít - notice_unable_delete_time_entry: Az időrögzítés nem törölhető - label_overall_spent_time: Összes ráfordított idő - field_time_entries: Idő rögzítés - project_module_gantt: Gantt - project_module_calendar: Naptár - button_edit_associated_wikipage: "Hozzárendelt Wiki oldal szerkesztése: %{page_title}" - field_text: Szöveg mező - label_user_mail_option_only_owner: Csak arról, aminek én vagyok a tulajdonosa - setting_default_notification_option: Alapértelmezett értesítési beállítások - label_user_mail_option_only_my_events: Csak az általam megfigyelt dolgokról vagy amiben részt veszek - label_user_mail_option_only_assigned: Csak a hozzámrendelt dolgokról - label_user_mail_option_none: Semilyen eseményről - field_member_of_group: Hozzárendelt csoport - field_assigned_to_role: Hozzárendelt szerepkör - notice_not_authorized_archived_project: A projekt, amihez hozzá szeretnél férni archiválva lett. - label_principal_search: "Felhasználó vagy csoport keresése:" - label_user_search: "Felhasználó keresése:" - field_visible: Látható - setting_emails_header: Emailek fejléce - setting_commit_logtime_activity_id: A rögzített időhöz tartozó tevékenység - text_time_logged_by_changeset: Alkalmazva a %{value} changeset-ben. - setting_commit_logtime_enabled: Időrögzítés engedélyezése - notice_gantt_chart_truncated: A diagram le lett vágva, mert elérte a maximálisan megjeleníthető elemek számát (%{max}) - setting_gantt_items_limit: A gantt diagrammon megjeleníthető maximális elemek száma - field_warn_on_leaving_unsaved: Figyelmeztessen, nem mentett módosításokat tartalmazó oldal elhagyásakor - text_warn_on_leaving_unsaved: A jelenlegi oldal nem mentett módosításokat tartalmaz, ami elvész, ha elhagyja az oldalt. - label_my_queries: Egyéni lekérdezéseim - text_journal_changed_no_detail: "%{label} módosítva" - label_news_comment_added: Megjegyzés hozzáadva a hírhez - button_expand_all: Mindet kibont - button_collapse_all: Mindet összecsuk - label_additional_workflow_transitions_for_assignee: További átmenetek engedélyezettek, ha a felhasználó a hozzárendelt - label_additional_workflow_transitions_for_author: További átmenetek engedélyezettek, ha a felhasználó a szerző - label_bulk_edit_selected_time_entries: A kiválasztott idő bejegyzések csoportos szerkesztése - text_time_entries_destroy_confirmation: Biztos benne, hogy törölni szeretné a kiválasztott idő bejegyzés(eke)t? - label_role_anonymous: Anonymous - label_role_non_member: Nem tag - label_issue_note_added: Jegyzet hozzáadva - label_issue_status_updated: Állapot módosítva - label_issue_priority_updated: Prioritás módosítva - label_issues_visibility_own: A felhasználó által létrehozott vagy hozzárendelt feladatok - field_issues_visibility: Feladatok láthatósága - label_issues_visibility_all: Minden feladat - permission_set_own_issues_private: Saját feladatok beállítása nyilvánosra vagy privátra - field_is_private: Privát - permission_set_issues_private: Feladatok beállítása nyilvánosra vagy privátra - label_issues_visibility_public: Minden nem privát feladat - text_issues_destroy_descendants_confirmation: Ezzel törölni fog %{count} alfeladatot is. - field_commit_logs_encoding: Commit üzenetek kódlapja - field_scm_path_encoding: Elérési útvonal kódlapja - text_scm_path_encoding_note: "Alapértelmezett: UTF-8" - field_path_to_repository: A repository elérési útja - field_root_directory: Gyökér könyvtár - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Helyi repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Parancs - text_scm_command_version: Verzió - 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 feladat - one: 1 feladat - other: "%{count} feladatok" - 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: mind - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Alprojektekkel - label_cross_project_tree: Projekt fával - label_cross_project_hierarchy: Projekt hierarchiával - label_cross_project_system: Minden projekttel - 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: Ö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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b7/b75d5f0ff60be5f80b39e65c645dcf825ec9984b.svn-base --- a/.svn/pristine/b7/b75d5f0ff60be5f80b39e65c645dcf825ec9984b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,462 +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. - -require 'redmine/scm/adapters/abstract_adapter' - -module Redmine - module Scm - module Adapters - class CvsAdapter < AbstractAdapter - - # CVS executable name - CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" - - class << self - def client_command - @@bin ||= CVS_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_above?([1, 12]) - 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) - 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 - - # Guidelines for the input: - # url -> the project-path, relative to the cvsroot (eg. module name) - # root_url -> the good old, sometimes damned, CVSROOT - # login -> unnecessary - # password -> unnecessary too - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - @url = url - # TODO: better Exception here (IllegalArgumentException) - raise CommandFailed if root_url.blank? - @root_url = root_url - - # These are unused. - @login = login if login && !login.empty? - @password = (password || "") if @login - end - - def path_encoding - @path_encoding - end - - def info - logger.debug " info" - Info.new({:root_url => @root_url, :lastrev => nil}) - end - - def get_previous_revision(revision) - CvsRevisionHelper.new(revision).prevRev - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - # this method is used by the repository-browser (aka LIST) - def entries(path=nil, identifier=nil, options={}) - logger.debug " entries '#{path}' with identifier '#{identifier}'" - path_locale = scm_iconv(@path_encoding, 'UTF-8', path) - path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding) - entries = Entries.new - cmd_args = %w|-q rls -e| - cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier - cmd_args << path_with_proj(path) - scm_cmd(*cmd_args) do |io| - io.each_line() do |line| - fields = line.chop.split('/',-1) - logger.debug(">>InspectLine #{fields.inspect}") - if fields[0]!="D" - time = nil - # Thu Dec 13 16:27:22 2007 - time_l = fields[-3].split(' ') - if time_l.size == 5 && time_l[4].length == 4 - begin - time = Time.parse( - "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}") - rescue - end - end - entries << Entry.new( - { - :name => scm_iconv('UTF-8', @path_encoding, fields[-5]), - #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), - :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"), - :kind => 'file', - :size => nil, - :lastrev => Revision.new( - { - :revision => fields[-4], - :name => scm_iconv('UTF-8', @path_encoding, fields[-4]), - :time => time, - :author => '' - }) - }) - else - entries << Entry.new( - { - :name => scm_iconv('UTF-8', @path_encoding, fields[1]), - :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"), - :kind => 'dir', - :size => nil, - :lastrev => nil - }) - end - end - end - entries.sort_by_name - rescue ScmCommandAborted - nil - end - - STARTLOG="----------------------------" - ENDLOG ="=============================================================================" - - # Returns all revisions found between identifier_from and identifier_to - # in the repository. both identifier have to be dates or nil. - # these method returns nothing but yield every result in block - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) - path_with_project_utf8 = path_with_proj(path) - path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8) - logger.debug " revisions path:" + - "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" - cmd_args = %w|-q rlog| - cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from - cmd_args << path_with_project_utf8 - scm_cmd(*cmd_args) do |io| - state = "entry_start" - commit_log = String.new - revision = nil - date = nil - author = nil - entry_path = nil - entry_name = nil - file_state = nil - branch_map = nil - io.each_line() do |line| - if state != "revision" && /^#{ENDLOG}/ =~ line - commit_log = String.new - revision = nil - state = "entry_start" - end - if state == "entry_start" - branch_map = Hash.new - if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line - entry_path = normalize_cvs_path($1) - entry_name = normalize_path(File.basename($1)) - logger.debug("Path #{entry_path} <=> Name #{entry_name}") - elsif /^head: (.+)$/ =~ line - entry_headRev = $1 #unless entry.nil? - elsif /^symbolic names:/ =~ line - state = "symbolic" #unless entry.nil? - elsif /^#{STARTLOG}/ =~ line - commit_log = String.new - state = "revision" - end - next - elsif state == "symbolic" - if /^(.*):\s(.*)/ =~ (line.strip) - branch_map[$1] = $2 - else - state = "tags" - next - end - elsif state == "tags" - if /^#{STARTLOG}/ =~ line - commit_log = "" - state = "revision" - elsif /^#{ENDLOG}/ =~ line - state = "head" - end - next - elsif state == "revision" - if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line - if revision - revHelper = CvsRevisionHelper.new(revision) - revBranch = "HEAD" - branch_map.each() do |branch_name, branch_point| - if revHelper.is_in_branch_with_symbol(branch_point) - revBranch = branch_name - end - end - logger.debug("********** YIELD Revision #{revision}::#{revBranch}") - yield Revision.new({ - :time => date, - :author => author, - :message => commit_log.chomp, - :paths => [{ - :revision => revision.dup, - :branch => revBranch.dup, - :path => scm_iconv('UTF-8', @path_encoding, entry_path), - :name => scm_iconv('UTF-8', @path_encoding, entry_name), - :kind => 'file', - :action => file_state - }] - }) - end - commit_log = String.new - revision = nil - if /^#{ENDLOG}/ =~ line - state = "entry_start" - end - next - end - - if /^branches: (.+)$/ =~ line - # TODO: version.branch = $1 - elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line - revision = $1 - elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line - date = Time.parse($1) - line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line) - author_utf8 = /author: ([^;]+)/.match(line_utf8)[1] - author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8) - file_state = /state: ([^;]+)/.match(line)[1] - # TODO: - # linechanges only available in CVS.... - # maybe a feature our SVN implementation. - # I'm sure, they are useful for stats or something else - # linechanges =/lines: \+(\d+) -(\d+)/.match(line) - # unless linechanges.nil? - # version.line_plus = linechanges[1] - # version.line_minus = linechanges[2] - # else - # version.line_plus = 0 - # version.line_minus = 0 - # end - else - commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ - end - end - end - end - rescue ScmCommandAborted - Revisions.new - end - - def diff(path, identifier_from, identifier_to=nil) - logger.debug " diff path:'#{path}'" + - ",identifier_from #{identifier_from}, identifier_to #{identifier_to}" - cmd_args = %w|rdiff -u| - cmd_args << "-r#{identifier_to}" - cmd_args << "-r#{identifier_from}" - cmd_args << path_with_proj(path) - diff = [] - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - diff << line - end - end - diff - rescue ScmCommandAborted - nil - end - - def cat(path, identifier=nil) - identifier = (identifier) ? identifier : "HEAD" - logger.debug " cat path:'#{path}',identifier #{identifier}" - cmd_args = %w|-q co| - cmd_args << "-D" << time_to_cvstime(identifier) if identifier - cmd_args << "-p" << path_with_proj(path) - cat = nil - scm_cmd(*cmd_args) do |io| - io.binmode - cat = io.read - end - cat - rescue ScmCommandAborted - nil - end - - def annotate(path, identifier=nil) - identifier = (identifier) ? identifier : "HEAD" - logger.debug " annotate path:'#{path}',identifier #{identifier}" - cmd_args = %w|rannotate| - cmd_args << "-D" << time_to_cvstime(identifier) if identifier - cmd_args << path_with_proj(path) - blame = Annotate.new - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} - blame.add_line( - $3.rstrip, - Revision.new( - :revision => $1, - :identifier => nil, - :author => $2.strip - )) - end - end - blame - rescue ScmCommandAborted - Annotate.new - end - - private - - # Returns the root url without the connexion string - # :pserver:anonymous@foo.bar:/path => /path - # :ext:cvsservername:/path => /path - def root_url_path - root_url.to_s.gsub(/^:.+:\d*/, '') - end - - # convert a date/time into the CVS-format - def time_to_cvstime(time) - return nil if time.nil? - time = Time.now if time == 'HEAD' - - unless time.kind_of? Time - time = Time.parse(time) - end - return time_to_cvstime_rlog(time) - end - - def time_to_cvstime_rlog(time) - return nil if time.nil? - t1 = time.clone.localtime - return t1.strftime("%Y-%m-%d %H:%M:%S") - end - - def normalize_cvs_path(path) - normalize_path(path.gsub(/Attic\//,'')) - end - - def normalize_path(path) - path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') - end - - def path_with_proj(path) - "#{url}#{with_leading_slash(path)}" - end - private :path_with_proj - - class Revision < Redmine::Scm::Adapters::Revision - # Returns the readable identifier - def format_identifier - revision.to_s - end - end - - def scm_cmd(*args, &block) - full_args = ['-d', root_url] - 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, "cvs exited with non-zero status: #{$?.exitstatus}" - end - ret - end - private :scm_cmd - end - - class CvsRevisionHelper - attr_accessor :complete_rev, :revision, :base, :branchid - - def initialize(complete_rev) - @complete_rev = complete_rev - parseRevision() - end - - def branchPoint - return @base - end - - def branchVersion - if isBranchRevision - return @base+"."+@branchid - end - return @base - end - - def isBranchRevision - !@branchid.nil? - end - - def prevRev - unless @revision == 0 - return buildRevision( @revision - 1 ) - end - return buildRevision( @revision ) - end - - def is_in_branch_with_symbol(branch_symbol) - bpieces = branch_symbol.split(".") - branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" - return ( branchVersion == branch_start ) - end - - private - def buildRevision(rev) - if rev == 0 - if @branchid.nil? - @base + ".0" - else - @base - end - elsif @branchid.nil? - @base + "." + rev.to_s - else - @base + "." + @branchid + "." + rev.to_s - end - end - - # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 - def parseRevision() - pieces = @complete_rev.split(".") - @revision = pieces.last.to_i - baseSize = 1 - baseSize += (pieces.size / 2) - @base = pieces[0..-baseSize].join(".") - if baseSize > 2 - @branchid = pieces[-2] - end - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b7/b789966309eb822c678528d8a6d3affdf01146bc.svn-base --- a/.svn/pristine/b7/b789966309eb822c678528d8a6d3affdf01146bc.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -# 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 WikiTest < ActiveSupport::TestCase - fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions - - def test_create - wiki = Wiki.new(:project => Project.find(2)) - assert !wiki.save - assert_equal 1, wiki.errors.count - - wiki.start_page = "Start page" - assert wiki.save - end - - def test_update - @wiki = Wiki.find(1) - @wiki.start_page = "Another start page" - assert @wiki.save - @wiki.reload - assert_equal "Another start page", @wiki.start_page - end - - def test_find_page_should_not_be_case_sensitive - wiki = Wiki.find(1) - page = WikiPage.find(2) - - assert_equal page, wiki.find_page('Another_page') - assert_equal page, wiki.find_page('Another page') - assert_equal page, wiki.find_page('ANOTHER page') - end - - def test_find_page_with_cyrillic_characters - wiki = Wiki.find(1) - page = WikiPage.find(10) - assert_equal page, wiki.find_page('Этика_менеджмента') - end - - def test_find_page_with_backslashes - wiki = Wiki.find(1) - page = WikiPage.create!(:wiki => wiki, :title => '2009\\02\\09') - assert_equal page, wiki.find_page('2009\\02\\09') - end - - def test_find_page_without_redirect - wiki = Wiki.find(1) - page = wiki.find_page('Another_page') - assert_not_nil page - assert_equal 'Another_page', page.title - assert_equal false, wiki.page_found_with_redirect? - end - - def test_find_page_with_redirect - wiki = Wiki.find(1) - WikiRedirect.create!(:wiki => wiki, :title => 'Old_title', :redirects_to => 'Another_page') - page = wiki.find_page('Old_title') - assert_not_nil page - assert_equal 'Another_page', page.title - assert_equal true, wiki.page_found_with_redirect? - end - - def test_titleize - ja_test = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" - ja_test.force_encoding('UTF-8') if ja_test.respond_to?(:force_encoding) - assert_equal 'Page_title_with_CAPITALES', Wiki.titleize('page title with CAPITALES') - assert_equal ja_test, Wiki.titleize(ja_test) - end - - context "#sidebar" do - setup do - @wiki = Wiki.find(1) - end - - should "return nil if undefined" do - assert_nil @wiki.sidebar - end - - should "return a WikiPage if defined" do - page = @wiki.pages.new(:title => 'Sidebar') - page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar') - page.save! - - assert_kind_of WikiPage, @wiki.sidebar - assert_equal 'Sidebar', @wiki.sidebar.title - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b7/b7e57087e2b7452a3c04ea867d9d27d95784c19d.svn-base --- a/.svn/pristine/b7/b7e57087e2b7452a3c04ea867d9d27d95784c19d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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] || "label_#{name}".to_sym - self.order = options[:order] || self.class.available_formats.size - 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(*args) - custom_field_format = args.first - unless custom_field_format.is_a?(Redmine::CustomFieldFormat) - custom_field_format = Redmine::CustomFieldFormat.new(*args) - end - @@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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b8/b8657f7d2e5a1ade105ba1c1a91e64ff816547b4.svn-base --- a/.svn/pristine/b8/b8657f7d2e5a1ade105ba1c1a91e64ff816547b4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,566 +0,0 @@ -# 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 ||= find(:first, :order => 'version') - end - - # find latest version of this record - def latest - @latest ||= find(:first, :order => 'version desc') - 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) - find :first, :order => 'version desc', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version] - 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] - 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.find :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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b8/b8adc96bc75bc39796f4d27ac3d2335820b193bb.svn-base --- a/.svn/pristine/b8/b8adc96bc75bc39796f4d27ac3d2335820b193bb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +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' %> - -<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> - -
    - - - <% line_num = 1; previous_revision = nil %> - <% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %> - <% revision = @annotate.revisions[line_num - 1] %> - - - - - - - <% line_num += 1; previous_revision = revision %> - <% end %> - -
    <%= line_num %> - <%= (revision.identifier ? link_to_revision(revision, @repository) : format_revision(revision)) if revision && revision != previous_revision %><%= h(revision.author.to_s.split('<').first) if revision && revision != previous_revision %>
    <%= line.html_safe %>
    -
    - -<% html_title(l(:button_annotate)) -%> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag 'scm' %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b9/b9350cafa5fd1809b5fffbaee5fa651925ff007b.svn-base --- a/.svn/pristine/b9/b9350cafa5fd1809b5fffbaee5fa651925ff007b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/b9/b97a924cfd29125d9c328b566de6a9a5dd6ba62d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b9/b97a924cfd29125d9c328b566de6a9a5dd6ba62d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,72 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::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 + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=handler' + end + + assert_response :success + assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_should_accept_jsonp_param + with_settings :jsonp_enabled => '1' do + get '/trackers.json?jsonp=handler' + end + + assert_response :success + assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_should_strip_invalid_characters_from_callback + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=+-aA$1_' + end + + assert_response :success + assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body + assert_equal 'application/javascript; charset=utf-8', response.headers['Content-Type'] + end + + def test_jsonp_without_callback_should_return_json + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=' + end + + assert_response :success + assert_match %r{^\{"trackers":.+\}$}, response.body + assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/b9/b9a8a43ee4546a78753a5fba634e4d8d0d32059d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/b9/b9a8a43ee4546a78753a5fba634e4d8d0d32059d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,49 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ba/ba17318dbb463f926062c32d7de0e2c9351f38c4.svn-base --- a/.svn/pristine/ba/ba17318dbb463f926062c32d7de0e2c9351f38c4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ - - - -RedmineWikiFormatting - - - - - -

    Wiki formatting

    - -

    Links

    - -

    Redmine links

    - -

    Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.

    -
      -
    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • -
    • Link to an issue note: #124-6, or #124#note-6
    • -
    - -

    Wiki links:

    - -
      -
    • [[Guide]] displays a link to the page named 'Guide': Guide
    • -
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • -
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual
    • -
    - -

    You can also link to pages of an other project wiki:

    - -
      -
    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • -
    • [[sandbox:]] displays a link to the Sandbox wiki main page
    • -
    - -

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    - -

    Links to other resources:

    - -
      -
    • Documents: -
        -
      • document#17 (link to document with id 17)
      • -
      • document:Greetings (link to the document with title "Greetings")
      • -
      • document:"Some document" (double quotes can be used when document title contains spaces)
      • -
      • sandbox:document:"Some document" (link to a document with title "Some document" in other project "sandbox")
      • -
    • -
    - -
      -
    • Versions: -
        -
      • version#3 (link to version with id 3)
      • -
      • version:1.0.0 (link to version named "1.0.0")
      • -
      • version:"1.0 beta 2"
      • -
      • sandbox:version:1.0.0 (link to version "1.0.0" in the project "sandbox")
      • -
    • -
    - -
      -
    • Attachments: -
        -
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • -
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
      • -
    • -
    - -
      -
    • Changesets: -
        -
      • r758 (link to a changeset)
      • -
      • commit:c6f4d0fd (link to a changeset with a non-numeric hash)
      • -
      • svn1|r758 (link to a changeset of a specific repository, for projects with multiple repositories)
      • -
      • commit:hg|c6f4d0fd (link to a changeset with a non-numeric hash of a specific repository)
      • -
      • sandbox:r758 (link to a changeset of another project)
      • -
      • sandbox:commit:c6f4d0fd (link to a changeset with a non-numeric hash of another project)
      • -
    • -
    - -
      -
    • Repository files: -
        -
      • source:some/file (link to the file located at /some/file in the project's repository)
      • -
      • source:some/file@52 (link to the file's revision 52)
      • -
      • source:some/file#L120 (link to line 120 of the file)
      • -
      • source:some/file@52#L120 (link to line 120 of the file's revision 52)
      • -
      • source:"some file@52#L120" (use double quotes when the URL contains spaces
      • -
      • export:some/file (force the download of the file)
      • -
      • source:svn1|some/file (link to a file of a specific repository, for projects with multiple repositories)
      • -
      • sandbox:source:some/file (link to the file located at /some/file in the repository of the project "sandbox")
      • -
      • sandbox:export:some/file (force the download of the file)
      • -
    • -
    - -
      -
    • Forum messages: -
        -
      • message#1218 (link to message with id 1218)
      • -
    • -
    - -
      -
    • Projects: -
        -
      • project#3 (link to project with id 3)
      • -
      • project:someproject (link to project named "someproject")
      • -
    • -
    - - -

    Escaping:

    - -
      -
    • You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !
    • -
    - - -

    External links

    - -

    HTTP URLs and email addresses are automatically turned into clickable links:

    - -
    -http://www.redmine.org, someone@foo.bar
    -
    - -

    displays: http://www.redmine.org,

    - -

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    - -
    -"Redmine web site":http://www.redmine.org
    -
    - -

    displays: Redmine web site

    - - -

    Text formatting

    - - -

    For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See http://en.wikipedia.org/wiki/Textile_(markup_language) for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    - -

    Font style

    - -
    -* *bold*
    -* _italic_
    -* _*bold italic*_
    -* +underline+
    -* -strike-through-
    -
    - -

    Display:

    - -
      -
    • bold
    • -
    • italic
    • -
    • *bold italic*
    • -
    • underline
    • -
    • strike-through
    • -
    - -

    Inline images

    - -
      -
    • !image_url! displays an image located at image_url (textile syntax)
    • -
    • !>image_url! right floating image
    • -
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!
    • -
    - -

    Headings

    - -
    -h1. Heading
    -h2. Subheading
    -h3. Subsubheading
    -
    - -

    Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    - - -

    Paragraphs

    - -
    -p>. right aligned
    -p=. centered
    -
    - -

    This is a centered paragraph.

    - - -

    Blockquotes

    - -

    Start the paragraph with bq.

    - -
    -bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    -To go live, all you need to add is a database and a web server.
    -
    - -

    Display:

    - -
    -

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    -
    - - -

    Table of content

    - -
    -{{toc}} => left aligned toc
    -{{>toc}} => right aligned toc
    -
    - -

    Macros

    - -

    Redmine has the following builtin macros:

    - -

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    - -
    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    - - -

    Code highlighting

    - -

    Default code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.

    - -

    You can highlight code in your wiki page using this syntax:

    - -
    -<pre><code class="ruby">
    -  Place you code here.
    -</code></pre>
    -
    - -

    Example:

    - -
     1 # The Greeter class
    - 2 class Greeter
    - 3   def initialize(name)
    - 4     @name = name.capitalize
    - 5   end
    - 6 
    - 7   def salute
    - 8     puts "Hello #{@name}!" 
    - 9   end
    -10 end
    -
    - - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ba/ba5d4789df17479bc2d584f1eb52a1d2b9bef079.svn-base --- a/.svn/pristine/ba/ba5d4789df17479bc2d584f1eb52a1d2b9bef079.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -<% 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}" %> - - <% 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))) %> - - -<%= 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) %>) diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ba/badebf4fa69fcda626a5e7a05f07c66ca17188a7.svn-base --- a/.svn/pristine/ba/badebf4fa69fcda626a5e7a05f07c66ca17188a7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -# This file was generated by the "jdbc" generator, which is provided -# by the activerecord-jdbc-adapter gem. -# -# This file allows you to use Rails' various db:* tasks with JDBC. -if defined?(JRUBY_VERSION) - require 'jdbc_adapter' - require 'jdbc_adapter/rake_tasks' -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ba/bae5f43e87eacd9d199692aa578d1d741f75b33e.svn-base --- a/.svn/pristine/ba/bae5f43e87eacd9d199692aa578d1d741f75b33e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 '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 - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bb/bb06370b97e92382be8f657caf2728943f14253a.svn-base --- a/.svn/pristine/bb/bb06370b97e92382be8f657caf2728943f14253a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bb/bb2db291d2956eb483816457a1bb37aed25066e3.svn-base --- a/.svn/pristine/bb/bb2db291d2956eb483816457a1bb37aed25066e3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1077 +0,0 @@ -# translated by Dzintars Bergs (dzintars.bergs@gmail.com) - -lv: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%d %b" - long: "%d %B %Y" - - day_names: [Svētdiena, Pirmdiena, Otrdiena, Trešdiena, Ceturtdiena, Piektdiena, Sestdiena] - abbr_day_names: [Sv, Pr, Ot, Tr, Ct, Pk, St] - - month_names: [~, Janvāris, Februāris, Marts, Aprīlis , Maijs, Jūnijs, Jūlijs, Augusts, Septembris, Oktobris, Novembris, Decembris] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Mai, Jūn, Jūl, Aug, Sep, Okt, Nov, Dec] - order: - - :day - - :month - - :year - - 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: "rītā" - pm: "vakarā" - - datetime: - distance_in_words: - half_a_minute: "pus minūte" - less_than_x_seconds: - one: "mazāk kā 1 sekunde" - other: "mazāk kā %{count} sekundes" - x_seconds: - one: "1 sekunde" - other: "%{count} sekundes" - less_than_x_minutes: - one: "mazāk kā minūte" - other: "mazāk kā %{count} minūtes" - x_minutes: - one: "1 minūte" - other: "%{count} minūtes" - about_x_hours: - one: "aptuveni 1 stunda" - other: "aptuveni %{count} stundas" - x_hours: - one: "1 hour" - other: "%{count} hours" - x_days: - one: "1 diena" - other: "%{count} dienas" - about_x_months: - one: "aptuveni 1 mēnesis" - other: "aptuveni %{count} mēneši" - x_months: - one: "1 mēnesis" - other: "%{count} mēneši" - about_x_years: - one: "aptuveni 1 gads" - other: "aptuveni %{count} gadi" - over_x_years: - one: "ilgāk par 1 gadu" - other: "ilgāk par %{count} gadiem" - almost_x_years: - one: "gandrīz 1 gadu" - other: "gandrīz %{count} gadus" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: " " - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Baits" - other: "Baiti" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: - sentence_connector: "un" - 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: "nav iekļauts sarakstā" - exclusion: "ir rezervēts" - invalid: "nederīgs" - confirmation: "apstiprinājums nesakrīt" - accepted: "jābūt akceptētam" - empty: "nevar būt tukšs" - blank: "nevar būt neaizpildīts" - too_long: "ir pārāk gara(š) (maksimālais garums ir %{count} simboli)" - too_short: "ir pārāk īsa(s) (minimālais garums ir %{count} simboli)" - wrong_length: "ir nepareiza garuma (vajadzētu būt %{count} simboli)" - taken: "eksistē" - not_a_number: "nav skaitlis" - not_a_date: "nav derīgs datums" - greater_than: "jābūt lielākam par %{count}" - greater_than_or_equal_to: "jābūt lielākam vai vienādam ar %{count}" - equal_to: "jābūt vienādam ar %{count}" - less_than: "jābūt mazākam kā %{count}" - less_than_or_equal_to: "jābūt mazākam vai vienādam ar %{count}" - odd: "jāatšķirās" - even: "jāsakrīt" - greater_than_start_date: "jābūt vēlākam par sākuma datumu" - not_same_project: "nepieder pie tā paša projekta" - circular_dependency: "Šī relācija radītu ciklisku atkarību" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Izvēlieties - - general_text_No: 'Nē' - general_text_Yes: 'Jā' - general_text_no: 'nē' - general_text_yes: 'jā' - general_lang_name: 'Latvian (Latviešu)' - 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: Konts tika atjaunots veiksmīgi. - notice_account_invalid_creditentials: Nepareizs lietotāja vārds vai parole. - notice_account_password_updated: Parole tika veiksmīgi atjaunota. - notice_account_wrong_password: Nepareiza parole - notice_account_register_done: Konts veiksmīgi izveidots. Lai aktivizētu kontu, spiediet uz saites, kas Jums tika nosūtīta. - notice_account_unknown_email: Nezināms lietotājs - notice_can_t_change_password: Šis konts izmanto ārēju pilnvarošanas avotu. Nav iespējams nomainīt paroli. - notice_account_lost_email_sent: Jums tika nosūtīts e-pasts ar instrukcijām, kā izveidot jaunu paroli. - notice_account_activated: Jūsu konts ir aktivizēts. Varat pieslēgties sistēmai. - notice_successful_create: Veiksmīga izveide. - notice_successful_update: Veiksmīga atjaunošana. - notice_successful_delete: Veiksmīga dzēšana. - notice_successful_connection: Veiksmīgs savienojums. - notice_file_not_found: Lapa, ko Jūs mēģināt atvērt, neeksistē vai ir pārvietota. - notice_locking_conflict: Datus ir atjaunojis cits lietotājs. - notice_not_authorized: Jums nav tiesību piekļūt šai lapai. - notice_email_sent: "E-pasts tika nosūtīts uz %{value}" - notice_email_error: "Kļūda sūtot e-pastu (%{value})" - notice_feeds_access_key_reseted: Jūsu RSS pieejas atslēga tika iestatīta sākuma stāvoklī. - notice_api_access_key_reseted: Jūsu API pieejas atslēga tika iestatīta sākuma stāvoklī. - notice_failed_to_save_issues: "Neizdevās saglabāt %{count} uzdevumu(us) no %{total} izvēlēti: %{ids}." - notice_no_issue_selected: "Nav izvēlēts uzdevums! Lūdzu, atzīmējiet uzdevumus, kurus vēlaties rediģēt!" - notice_account_pending: "Jūsu konts tika izveidots un šobrīd gaida administratora apstiprinājumu." - notice_default_data_loaded: Noklusētā konfigurācija tika veiksmīgi ielādēta. - notice_unable_delete_version: Neizdevās dzēst versiju. - notice_issue_done_ratios_updated: Uzdevuma izpildes koeficients atjaunots. - - error_can_t_load_default_data: "Nevar ielādēt noklusētos konfigurācijas datus: %{value}" - error_scm_not_found: "Ieraksts vai versija nebija repozitorijā." - error_scm_command_failed: "Mēģinot piekļūt repozitorijam, notika kļūda: %{value}" - error_scm_annotate: "Ieraksts neeksistē vai tam nevar tikt pievienots paskaidrojums." - error_issue_not_found_in_project: 'Uzdevums netika atrasts vai nepieder šim projektam.' - error_no_tracker_in_project: 'Neviens trakeris nav saistīts ar šo projektu. Pārbaudiet projekta iestatījumus.' - error_no_default_issue_status: 'Nav definēts uzdevuma noklusētais statuss. Pārbaudiet konfigurāciju (Ejat uz: "Administrācija -> Uzdevumu statusi")!' - error_can_not_reopen_issue_on_closed_version: 'Nevar pievienot atsauksmi uzdevumam, kas saistīts ar slēgtu versiju.' - error_can_not_archive_project: Šis projekts nevar tikt arhivēts - error_issue_done_ratios_not_updated: "Uzdevuma izpildes koeficients nav atjaunots." - error_workflow_copy_source: 'Lūdzu izvēlieties avota trakeri vai lomu' - error_workflow_copy_target: 'Lūdzu izvēlēties mērķa trakeri(us) un lomu(as)' - - warning_attachments_not_saved: "%{count} datnes netika saglabātas." - - mail_subject_lost_password: "Jūsu %{value} parole" - mail_body_lost_password: 'Lai mainītu paroli, spiediet uz šīs saites:' - mail_subject_register: "Jūsu %{value} konta aktivizācija" - mail_body_register: 'Lai izveidotu kontu, spiediet uz šīs saites:' - mail_body_account_information_external: "Varat izmantot Jūsu %{value} kontu, lai pieslēgtos." - mail_body_account_information: Jūsu konta informācija - mail_subject_account_activation_request: "%{value} konta aktivizācijas pieprasījums" - mail_body_account_activation_request: "Jauns lietotājs (%{value}) ir reģistrēts. Lietotāja konts gaida Jūsu apstiprinājumu:" - mail_subject_reminder: "%{count} uzdevums(i) sagaidāms(i) tuvākajās %{days} dienās" - mail_body_reminder: "%{count} uzdevums(i), kurš(i) ir nozīmēts(i) Jums, sagaidāms(i) tuvākajās %{days} dienās:" - mail_subject_wiki_content_added: "'%{id}' Wiki lapa pievienota" - mail_body_wiki_content_added: "The '%{id}' Wiki lapu pievienojis %{author}." - 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 - field_summary: Kopsavilkums - field_is_required: Nepieciešams - field_firstname: Vārds - field_lastname: Uzvārds - field_mail: "E-pasts" - field_filename: Datne - field_filesize: Izmērs - field_downloads: Lejupielādes - field_author: Autors - field_created_on: Izveidots - field_updated_on: Atjaunots - field_field_format: Formāts - field_is_for_all: Visiem projektiem - field_possible_values: Iespējamās vērtības - field_regexp: Regulārā izteiksme - field_min_length: Minimālais garums - field_max_length: Maksimālais garums - field_value: Vērtība - field_category: Kategorija - field_title: Nosaukums - field_project: Projekts - field_issue: Uzdevums - field_status: Statuss - field_notes: Piezīmes - field_is_closed: Uzdevums slēgts - field_is_default: Noklusētā vērtība - field_tracker: Trakeris - field_subject: Temats - field_due_date: Sagaidāmais datums - field_assigned_to: Piešķirts - field_priority: Prioritāte - field_fixed_version: Mērķa versija - field_user: Lietotājs - field_role: Loma - field_homepage: Vietne - field_is_public: Publisks - field_parent: Apakšprojekts projektam - field_is_in_roadmap: Ceļvedī parādītie uzdevumi - field_login: Pieslēgties - field_mail_notification: "E-pasta paziņojumi" - field_admin: Administrators - field_last_login_on: Pēdējo reizi pieslēdzies - field_language: Valoda - field_effective_date: Datums - field_password: Parole - field_new_password: Janā parole - field_password_confirmation: Paroles apstiprinājums - field_version: Versija - field_type: Tips - field_host: Hosts - field_port: Ports - field_account: Konts - field_base_dn: Base DN - field_attr_login: Pieslēgšanās atribūts - field_attr_firstname: Vārda atribūts - field_attr_lastname: Uzvārda atribūts - field_attr_mail: "E-pasta atribūts" - field_onthefly: "Lietotāja izveidošana on-the-fly" - field_start_date: Sākuma datums - field_done_ratio: "% padarīti" - field_auth_source: Pilnvarošanas režīms - field_hide_mail: "Paslēpt manu e-pasta adresi" - field_comments: Komentārs - field_url: URL - field_start_page: Sākuma lapa - field_subproject: Apakšprojekts - field_hours: Stundas - field_activity: Aktivitāte - field_spent_on: Datums - field_identifier: Identifikators - field_is_filter: Izmantots kā filtrs - field_issue_to: Saistīts uzdevums - field_delay: Kavējums - field_assignable: Uzdevums var tikt piesaistīts šai lomai - field_redirect_existing_links: Pāradresēt eksistējošās saites - field_estimated_hours: Paredzētais laiks - field_column_names: Kolonnas - field_time_zone: Laika zona - field_searchable: Meklējams - field_default_value: Noklusētā vērtība - field_comments_sorting: Rādīt komentārus - field_parent_title: Vecāka lapa - field_editable: Rediģējams - field_watcher: Vērotājs - field_identity_url: OpenID URL - field_content: Saturs - field_group_by: Grupēt rezultātus pēc - field_sharing: Koplietošana - - setting_app_title: Programmas nosaukums - setting_app_subtitle: Programmas apakš-nosaukums - setting_welcome_text: Sveiciena teksts - setting_default_language: Noklusētā valoda - setting_login_required: Nepieciešama pilnvarošana - setting_self_registration: Pašreģistrēšanās - setting_attachment_max_size: Pielikuma maksimālais izmērs - setting_issues_export_limit: Uzdevumu eksporta ierobežojums - setting_mail_from: "E-pasta adrese informācijas nosūtīšanai" - setting_bcc_recipients: "Saņēmēju adreses neparādīsies citu saņēmēju vēstulēs (bcc)" - setting_plain_text_mail: "Vēstule brīvā tekstā (bez HTML)" - setting_host_name: Hosta nosaukums un piekļuves ceļš - setting_text_formatting: Teksta formatēšana - setting_wiki_compression: Wiki vēstures saspiešana - setting_feeds_limit: Barotnes satura ierobežojums - setting_default_projects_public: Jaunie projekti noklusēti ir publiski pieejami - setting_autofetch_changesets: "Automātiski lietot jaunāko versiju, pieslēdzoties repozitorijam (Autofetch)" - setting_sys_api_enabled: Ieslēgt WS repozitoriju menedžmentam - setting_commit_ref_keywords: Norādes atslēgvārdi - setting_commit_fix_keywords: Fiksējošie atslēgvārdi - setting_autologin: Automātiskā pieslēgšanās - setting_date_format: Datuma formāts - setting_time_format: Laika formāts - setting_cross_project_issue_relations: "Atļaut starp-projektu uzdevumu relācijas" - setting_issue_list_default_columns: Noklusēti rādītās kolonnas uzdevumu sarakstā - setting_emails_footer: "E-pastu kājene" - setting_protocol: Protokols - setting_per_page_options: Objekti vienā lapā - setting_user_format: Lietotāju rādīšanas formāts - setting_activity_days_default: Dienus skaits aktivitāšu rādīšanai aktivitāšu sadaļā - setting_display_subprojects_issues: Rādīt apakšprojekta uzdevumus galvenajā projektā pēc noklusējuma - setting_enabled_scm: Lietot SCM - setting_mail_handler_body_delimiters: "Saīsināt pēc vienas no šim rindām" - setting_mail_handler_api_enabled: "Lietot WS ienākošajiem e-pastiem" - setting_mail_handler_api_key: API atslēga - setting_sequential_project_identifiers: Ģenerēt secīgus projektu identifikatorus - setting_gravatar_enabled: Izmantot Gravatar lietotāju ikonas - setting_gravatar_default: Noklusētais Gravatar attēls - setting_diff_max_lines_displayed: Maksimālais rādīto diff rindu skaits - setting_file_max_size_displayed: Maksimālais izmērs iekļautajiem teksta failiem - setting_repository_log_display_limit: Maksimālais žurnāla datnē rādīto revīziju skaits - setting_openid: Atļaut OpenID pieslēgšanos un reģistrēšanos - setting_password_min_length: Minimālais paroles garums - setting_new_project_user_role_id: Loma, kura tiek piešķirta ne-administratora lietotājam, kurš izveido projektu - setting_default_projects_modules: Noklusētie lietotie moduļi jaunam projektam - setting_issue_done_ratio: Aprēķināt uzdevuma izpildes koeficientu ar - setting_issue_done_ratio_issue_field: uzdevuma lauku - setting_issue_done_ratio_issue_status: uzdevuma statusu - setting_start_of_week: Sākt kalendāru ar - setting_rest_api_enabled: Lietot REST web-servisu - setting_cache_formatted_text: Kešot formatētu tekstu - - permission_add_project: Izveidot projektu - permission_add_subprojects: Izveidot apakšprojektu - permission_edit_project: Rediģēt projektu - permission_select_project_modules: Izvēlēties projekta moduļus - permission_manage_members: Pārvaldīt dalībniekus - permission_manage_project_activities: Pārvaldīt projekta aktivitātes - permission_manage_versions: Pārvaldīt versijas - permission_manage_categories: Pārvaldīt uzdevumu kategorijas - permission_view_issues: Apskatīt uzdevumus - permission_add_issues: Pievienot uzdevumus - permission_edit_issues: Rediģēt uzdevumus - permission_manage_issue_relations: Pārvaldīt uzdevumu relācijas - permission_add_issue_notes: Pievienot piezīmes - permission_edit_issue_notes: Rediģēt piezīmes - permission_edit_own_issue_notes: Rediģēt paša piezīmes - permission_move_issues: Pārvietot uzdevumus - permission_delete_issues: Dzēst uzdevumus - permission_manage_public_queries: Pārvaldīt publiskos pieprasījumus - permission_save_queries: Saglabāt pieprasījumus - permission_view_gantt: Skatīt Ganta diagrammu - permission_view_calendar: Skatīt kalendāru - permission_view_issue_watchers: Skatīt vērotāju sarakstu - permission_add_issue_watchers: Pievienot vērotājus - permission_delete_issue_watchers: Dzēst vērotājus - permission_log_time: Piereģistrēt pavadīto laiku - permission_view_time_entries: Skatīt pavadīto laiku - permission_edit_time_entries: Rdiģēt laika reģistrus - 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 - permission_manage_wiki: Pārvaldīt wiki - permission_rename_wiki_pages: Pārsaukt wiki lapas - permission_delete_wiki_pages: Dzēst wiki lapas - permission_view_wiki_pages: Skatīt wiki - permission_view_wiki_edits: Skatīt wiki vēsturi - permission_edit_wiki_pages: Rdiģēt wiki lapas - permission_delete_wiki_pages_attachments: Dzēst pielikumus - permission_protect_wiki_pages: Projekta wiki lapas - permission_manage_repository: Pārvaldīt repozitoriju - permission_browse_repository: Pārlūkot repozitoriju - permission_view_changesets: Skatīt izmaiņu kopumus - permission_commit_access: Atļaut piekļuvi - permission_manage_boards: Pārvaldīt ziņojumu dēļus - permission_view_messages: Skatīt ziņas - permission_add_messages: Publicēt ziņas - permission_edit_messages: Rediģēt ziņas - permission_edit_own_messages: Rediģēt savas ziņas - permission_delete_messages: Dzēst ziņas - permission_delete_own_messages: Dzēst savas ziņas - permission_export_wiki_pages: Eksportēt Wiki lapas - - project_module_issue_tracking: Uzdevumu uzskaite - project_module_time_tracking: Laika uzskaite - project_module_news: Jaunumi - project_module_documents: Dokumenti - project_module_files: Datnes - project_module_wiki: Wiki - project_module_repository: Repozitorijs - project_module_boards: Ziņojumu dēļi - - label_user: Lietotājs - label_user_plural: Lietotāji - label_user_new: Jauns lietotājs - label_user_anonymous: Anonīms - label_project: Projekts - label_project_new: Jauns projekts - label_project_plural: Projekti - label_x_projects: - zero: nav projektu - one: 1 projekts - other: "%{count} projekti" - label_project_all: Visi projekti - label_project_latest: Jaunākie projekti - label_issue: Uzdevums - label_issue_new: Jauns uzdevums - label_issue_plural: Uzdevumi - label_issue_view_all: Skatīt visus uzdevumus - label_issues_by: "Kārtot pēc %{value}" - label_issue_added: Uzdevums pievienots - label_issue_updated: Uzdevums atjaunots - label_document: Dokuments - label_document_new: Jauns dokuments - label_document_plural: Dokumenti - label_document_added: Dokuments pievienots - label_role: Loma - label_role_plural: Lomas - label_role_new: Jauna loma - label_role_and_permissions: Lomas un atļaujas - label_member: Dalībnieks - label_member_new: Jauns dalībnieks - label_member_plural: Dalībnieki - label_tracker: Trakeris - label_tracker_plural: Trakeri - label_tracker_new: Jauns trakeris - label_workflow: Darba gaita - label_issue_status: Uzdevuma statuss - label_issue_status_plural: Uzdevumu statusi - label_issue_status_new: Jauns statuss - label_issue_category: Uzdevuma kategorija - label_issue_category_plural: Uzdevumu kategorijas - label_issue_category_new: Jauna kategorija - label_custom_field: Pielāgojams lauks - label_custom_field_plural: Pielāgojami lauki - label_custom_field_new: Jauns pielāgojams lauks - label_enumerations: Uzskaitījumi - label_enumeration_new: Jauna vērtība - label_information: Informācija - label_information_plural: Informācija - label_please_login: Lūdzu pieslēdzieties - label_register: Reģistrēties - label_login_with_open_id_option: vai pieslēgties ar OpenID - label_password_lost: Nozaudēta parole - label_home: Sākums - label_my_page: Mana lapa - label_my_account: Mans konts - label_my_projects: Mani projekti - label_administration: Administrācija - label_login: Pieslēgties - label_logout: Atslēgties - label_help: Palīdzība - label_reported_issues: Ziņotie uzdevumi - label_assigned_to_me_issues: Man piesaistītie uzdevumi - label_last_login: Pēdējā pieslēgšanās - label_registered_on: Reģistrējies - label_activity: Aktivitāte - label_overall_activity: Kopējās aktivitātes - label_user_activity: "Lietotāja %{value} aktivitātes" - label_new: Jauns - label_logged_as: Pieslēdzies kā - label_environment: Vide - label_authentication: Pilnvarošana - label_auth_source: Pilnvarošanas režīms - label_auth_source_new: Jauns pilnvarošanas režīms - label_auth_source_plural: Pilnvarošanas režīmi - label_subproject_plural: Apakšprojekti - label_subproject_new: Jauns apakšprojekts - label_and_its_subprojects: "%{value} un tā apakšprojekti" - label_min_max_length: Minimālais - Maksimālais garums - label_list: Saraksts - label_date: Datums - label_integer: Vesels skaitlis - label_float: Decimālskaitlis - label_boolean: Patiesuma vērtība - label_string: Teksts - 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 - label_attachment: Pielikums - label_attachment_new: Jauns pielikums - label_attachment_delete: Dzēst pielikumu - label_attachment_plural: Pielikumi - label_file_added: Lauks pievienots - label_report: Atskaite - label_report_plural: Atskaites - label_news: Ziņa - label_news_new: Pievienot ziņu - label_news_plural: Ziņas - label_news_latest: Jaunākās ziņas - label_news_view_all: Skatīt visas ziņas - label_news_added: Ziņas pievienotas - label_settings: Iestatījumi - label_overview: Pārskats - label_version: Versija - label_version_new: Jauna versija - label_version_plural: Versijas - label_close_versions: Aizvērt pabeigtās versijas - label_confirmation: Apstiprinājums - label_export_to: 'Pieejams arī:' - label_read: Lasīt... - label_public_projects: Publiskie projekti - label_open_issues: atvērts - label_open_issues_plural: atvērti - label_closed_issues: slēgts - label_closed_issues_plural: slēgti - label_x_open_issues_abbr_on_total: - zero: 0 atvērti / %{total} - one: 1 atvērts / %{total} - other: "%{count} atvērti / %{total}" - label_x_open_issues_abbr: - zero: 0 atvērti - one: 1 atvērts - other: "%{count} atvērti" - label_x_closed_issues_abbr: - zero: 0 slēgti - one: 1 slēgts - other: "%{count} slēgti" - label_total: Kopā - label_permissions: Atļaujas - label_current_status: Pašreizējais statuss - label_new_statuses_allowed: Jauni statusi atļauti - label_all: visi - label_none: neviens - label_nobody: nekas - label_next: Nākošais - label_previous: Iepriekšējais - label_used_by: Izmanto - label_details: Detaļas - label_add_note: Pievienot piezīmi - label_per_page: katrā lapā - label_calendar: Kalendārs - label_months_from: mēneši no - label_gantt: Ganta diagramma - label_internal: Iekšējais - label_last_changes: "pēdējās %{count} izmaiņas" - label_change_view_all: Skatīt visas izmaiņas - label_personalize_page: Pielāgot šo lapu - label_comment: Komentārs - label_comment_plural: Komentāri - label_x_comments: - zero: nav komentāru - one: 1 komentārs - other: "%{count} komentāri" - label_comment_add: Pievienot komentāru - label_comment_added: Komentārs pievienots - label_comment_delete: Dzēst komentārus - label_query: Pielāgots pieprasījums - label_query_plural: Pielāgoti pieprasījumi - label_query_new: Jauns pieprasījums - label_filter_add: Pievienot filtru - label_filter_plural: Filtri - label_equals: ir - label_not_equals: nav - label_in_less_than: ir mazāk kā - label_in_more_than: ir vairāk kā - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: iekš - label_today: šodien - label_all_time: visu laiku - label_yesterday: vakar - label_this_week: šonedēļ - label_last_week: pagājušo šonedēļ - label_last_n_days: "pēdējās %{count} dienas" - label_this_month: šomēnes - label_last_month: pagājušo mēnes - label_this_year: šogad - label_date_range: Datumu apgabals - label_less_than_ago: mazāk kā dienas iepriekš - label_more_than_ago: vairāk kā dienas iepriekš - label_ago: dienas iepriekš - label_contains: satur - label_not_contains: nesatur - label_day_plural: dienas - 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 - label_revision_plural: Revīzijas - label_revision_id: "Revīzija %{value}" - label_associated_revisions: Saistītās revīzijas - label_added: pievienots - label_modified: modificēts - label_copied: nokopēts - label_renamed: pārsaukts - label_deleted: dzēsts - label_latest_revision: Pēdējā revīzija - label_latest_revision_plural: Pēdējās revīzijas - label_view_revisions: Skatīt revīzijas - label_view_all_revisions: Skatīt visas revīzijas - label_max_size: Maksimālais izmērs - label_sort_highest: Pārvietot uz augšu - label_sort_higher: Pārvietot soli augšup - label_sort_lower: Pārvietot uz leju - label_sort_lowest: Pārvietot vienu soli uz leju - label_roadmap: Ceļvedis - label_roadmap_due_in: "Sagaidāms pēc %{value}" - label_roadmap_overdue: "nokavēts %{value}" - label_roadmap_no_issues: Šai versijai nav uzdevumu - label_search: Meklēt - label_result_plural: Rezultāti - label_all_words: Visi vārdi - label_wiki: Wiki - label_wiki_edit: Wiki labojums - label_wiki_edit_plural: Wiki labojumi - label_wiki_page: Wiki lapa - label_wiki_page_plural: Wiki lapas - label_index_by_title: Indeksēt pēc nosaukuma - label_index_by_date: Indeksēt pēc datuma - label_current_version: Tekošā versija - label_preview: Priekšskatījums - label_feed_plural: Barotnes - label_changes_details: Visu izmaiņu detaļas - label_issue_tracking: Uzdevumu uzskaite - label_spent_time: Pavadītais laiks - label_f_hour: "%{value} stunda" - label_f_hour_plural: "%{value} stundas" - label_time_tracking: Laika uzskaite - label_change_plural: Izmaiņas - label_statistics: Statistika - label_commits_per_month: Nodevumi mēnesī - label_commits_per_author: Nodevumi no autora - label_view_diff: Skatīt atšķirības - label_diff_inline: iekļauts - label_diff_side_by_side: blakus - label_options: Opcijas - label_copy_workflow_from: Kopēt darba plūsmu no - label_permissions_report: Atļauju atskaite - label_watched_issues: Vērotie uzdevumi - label_related_issues: Saistītie uzdevumi - label_applied_status: Piešķirtais statuss - label_loading: Lādējas... - label_relation_new: Jauna relācija - label_relation_delete: Dzēst relāciju - label_relates_to: saistīts ar - label_duplicates: dublikāti - label_duplicated_by: dublējas ar - label_blocks: bloķē - label_blocked_by: nobloķējis - label_precedes: pirms - label_follows: seko - label_end_to_start: no beigām uz sākumu - label_end_to_end: no beigām uz beigām - label_start_to_start: no sākuma uz sākumu - label_start_to_end: no sākuma uz beigām - label_stay_logged_in: Atcerēties mani - label_disabled: izslēgts - label_show_completed_versions: Rādīt pabeigtās versijas - label_me: es - label_board: Forums - label_board_new: Jauns forums - label_board_plural: Forumi - label_board_locked: Slēgts - label_board_sticky: Svarīgs - label_topic_plural: Tēmas - label_message_plural: Ziņas - label_message_last: Pēdējā ziņa - label_message_new: Jauna ziņa - label_message_posted: Ziņa pievienota - label_reply_plural: Atbildes - label_send_information: Sūtīt konta informāciju lietotājam - label_year: Gads - label_month: Mēnesis - label_week: Nedēļa - label_date_from: No - label_date_to: Kam - label_language_based: Izmantot lietotāja valodu - label_sort_by: "Kārtot pēc %{value}" - label_send_test_email: "Sūtīt testa e-pastu" - label_feeds_access_key: RSS piekļuves atslēga - label_missing_feeds_access_key: Trūkst RSS piekļuves atslēgas - label_feeds_access_key_created_on: "RSS piekļuves atslēga izveidota pirms %{value}" - label_module_plural: Moduļi - label_added_time_by: "Pievienojis %{author} pirms %{age}" - label_updated_time_by: "Atjaunojis %{author} pirms %{age}" - label_updated_time: "Atjaunots pirms %{value}" - label_jump_to_a_project: Pāriet uz projektu... - label_file_plural: Datnes - label_changeset_plural: Izmaiņu kopumi - label_default_columns: Noklusētās kolonnas - label_no_change_option: (Nav izmaiņu) - label_bulk_edit_selected_issues: Labot visus izvēlētos uzdevumus - label_theme: Tēma - label_default: Noklusēts - label_search_titles_only: Meklēt tikai nosaukumos - label_user_mail_option_all: "Par visiem notikumiem visos manos projektos" - label_user_mail_option_selected: "Par visiem notikumiem tikai izvēlētajos projektos..." - label_user_mail_no_self_notified: "Neziņot man par izmaiņām, kuras veicu es pats" - label_registration_activation_by_email: "konta aktivizācija caur e-pastu" - label_registration_manual_activation: manuālā konta aktivizācija - label_registration_automatic_activation: automātiskā konta aktivizācija - label_display_per_page: "Rādīt vienā lapā: %{value}" - label_age: Vecums - label_change_properties: Mainīt atribūtus - label_general: Galvenais - label_more: Vēl - label_scm: SCM - label_plugins: Spraudņi - label_ldap_authentication: LDAP pilnvarošana - label_downloads_abbr: L-lād. - label_optional_description: "Apraksts (neobligāts)" - label_add_another_file: Pievienot citu failu - label_preferences: Priekšrocības - label_chronological_order: Hronoloģiskā kārtībā - label_reverse_chronological_order: Apgriezti hronoloģiskā kārtībā - label_planning: Plānošana - label_incoming_emails: "Ienākošie e-pasti" - label_generate_key: Ģenerēt atslēgu - label_issue_watchers: Vērotāji - label_example: Piemērs - label_display: Rādīt - label_sort: Kārtot - label_ascending: Augoši - label_descending: Dilstoši - label_date_from_to: "No %{start} līdz %{end}" - label_wiki_content_added: Wiki lapa pievienota - label_wiki_content_updated: Wiki lapa atjaunota - label_group: Grupa - label_group_plural: Grupas - label_group_new: Jauna grupa - label_time_entry_plural: Pavadītais laiks - label_version_sharing_none: Nav koplietošanai - label_version_sharing_descendants: Ar apakšprojektiem - label_version_sharing_hierarchy: Ar projektu hierarhiju - label_version_sharing_tree: Ar projekta koku - label_version_sharing_system: Ar visiem projektiem - label_update_issue_done_ratios: Atjaunot uzdevuma veikuma attiecību - label_copy_source: Avots - label_copy_target: Mērķis - label_copy_same_as_target: Tāds pats kā mērķis - label_display_used_statuses_only: "Rādīt tikai statusus, ko lieto šis trakeris" - label_api_access_key: API pieejas atslēga - label_missing_api_access_key: Trūkst API pieejas atslēga - label_api_access_key_created_on: "API pieejas atslēga izveidota pirms %{value}" - - button_login: Pieslēgties - button_submit: Nosūtīt - button_save: Saglabāt - button_check_all: Atzīmēt visu - button_uncheck_all: Noņemt visus atzīmējumus - button_delete: Dzēst - button_create: Izveidot - button_create_and_continue: Izveidot un turpināt - button_test: Testēt - button_edit: Labot - button_add: Pievienot - button_change: Mainīt - button_apply: Apstiprināt - button_clear: Notīrīt - button_lock: Slēgt - button_unlock: Atslēgt - button_download: Lejuplādēt - button_list: Saraksts - button_view: Skats - button_move: Pārvietot - button_move_and_follow: Pārvietot un sekot - button_back: Atpakaļ - button_cancel: Atcelt - button_activate: Aktivizēt - button_sort: Kārtot - button_log_time: Ilgs laiks - button_rollback: Atjaunot uz šo versiju - button_watch: Vērot - button_unwatch: Nevērot - button_reply: Atbildēt - button_archive: Arhivēt - button_unarchive: Atarhivēt - button_reset: Atiestatīt - button_rename: Pārsaukt - button_change_password: Mainīt paroli - button_copy: Kopēt - button_copy_and_follow: Kopēt un sekot - button_annotate: Pierakstīt paskaidrojumu - button_update: Atjaunot - button_configure: Konfigurēt - button_quote: Citāts - button_duplicate: Dublēt - button_show: Rādīt - - status_active: aktīvs - status_registered: reģistrēts - status_locked: slēgts - - version_status_open: atvērta - version_status_locked: slēgta - version_status_closed: aizvērta - - field_active: Aktīvs - - text_select_mail_notifications: "Izvēlieties darbības, par kurām vēlaties saņemt ziņojumus e-pastā" - text_regexp_info: "piem. ^[A-Z0-9]+$" - text_min_max_length_info: "0 nozīmē, ka nav ierobežojumu" - text_project_destroy_confirmation: "Vai tiešām vēlaties dzēst šo projektu un ar to saistītos datus?" - text_subprojects_destroy_warning: "Tā apakšprojekts(i): %{value} arī tiks dzēsts(i)." - text_workflow_edit: Lai labotu darba plūsmu, izvēlieties lomu un trakeri - text_are_you_sure: "Vai esat pārliecināts?" - text_journal_changed: "%{label} mainīts no %{old} uz %{new}" - text_journal_set_to: "%{label} iestatīts uz %{value}" - text_journal_deleted: "%{label} dzēsts (%{old})" - text_journal_added: "%{label} %{value} pievienots" - text_tip_issue_begin_day: uzdevums sākas šodien - text_tip_issue_end_day: uzdevums beidzas šodien - text_tip_issue_begin_end_day: uzdevums sākas un beidzas šodien - text_caracters_maximum: "%{count} simboli maksimāli." - text_caracters_minimum: "Jābūt vismaz %{count} simbolu garumā." - text_length_between: "Garums starp %{min} un %{max} simboliem." - text_tracker_no_workflow: Šim trakerim nav definēta darba plūsma - text_unallowed_characters: Neatļauti simboli - text_comma_separated: "Atļautas vairākas vērtības (atdalīt ar komatu)." - text_line_separated: "Atļautas vairākas vērtības (rakstīt katru savā rindā)." - text_issues_ref_in_commit_messages: "Izmaiņu salīdzināšana izejot no ziņojumiem" - text_issue_added: "Uzdevumu %{id} pievienojis %{author}." - text_issue_updated: "Uzdevumu %{id} atjaunojis %{author}." - text_wiki_destroy_confirmation: "Vai esat drošs, ka vēlaties dzēst šo wiki un visu tās saturu?" - text_issue_category_destroy_question: "Daži uzdevumi (%{count}) ir nozīmēti šai kategorijai. Ko Jūs vēlaties darīt?" - text_issue_category_destroy_assignments: Dzēst kategoriju nozīmējumus - text_issue_category_reassign_to: Nozīmēt uzdevumus šai kategorijai - text_user_mail_option: "No neizvēlētajiem projektiem Jūs saņemsiet ziņojumus e-pastā tikai par notikumiem, kuriem Jūs sekojat vai kuros esat iesaistīts." - text_no_configuration_data: "Lomas, trakeri, uzdevumu statusi un darba plūsmas vēl nav konfigurētas.\nĻoti ieteicams ielādēt noklusēto konfigurāciju. Pēc ielādēšanas to būs iespējams modificēt." - text_load_default_configuration: Ielādēt noklusēto konfigurāciju - text_status_changed_by_changeset: "Apstiprināts izmaiņu kopumā %{value}." - text_issues_destroy_confirmation: 'Vai tiešām vēlaties dzēst izvēlēto uzdevumu(us)?' - text_select_project_modules: 'Izvēlieties moduļus šim projektam:' - text_default_administrator_account_changed: Noklusētais administratora konts mainīts - text_file_repository_writable: Pielikumu direktorijā atļauts rakstīt - text_plugin_assets_writable: Spraudņu kataloga direktorijā atļauts rakstīt - text_rmagick_available: "RMagick pieejams (neobligāts)" - text_destroy_time_entries_question: "%{hours} stundas tika ziņotas par uzdevumu, ko vēlaties dzēst. Ko darīt?" - text_destroy_time_entries: Dzēst ziņotās stundas - text_assign_time_entries_to_project: Piešķirt ziņotās stundas projektam - text_reassign_time_entries: 'Piešķirt ziņotās stundas uzdevumam:' - text_user_wrote: "%{value} rakstīja:" - text_enumeration_destroy_question: "%{count} objekti ir piešķirti šai vērtībai." - text_enumeration_category_reassign_to: 'Piešķirt tos šai vērtībai:' - text_email_delivery_not_configured: "E-pastu nosūtīšana nav konfigurēta, un ziņojumi ir izslēgti.\nKonfigurējiet savu SMTP serveri datnē config/configuration.yml un pārstartējiet lietotni." - text_repository_usernames_mapping: "Izvēlieties vai atjaunojiet Redmine lietotāju, saistītu ar katru lietotājvārdu, kas atrodams repozitorija žurnālā.\nLietotāji ar to pašu Redmine un repozitorija lietotājvārdu būs saistīti automātiski." - text_diff_truncated: '... Šis diff tika nošķelts, jo tas pārsniedz maksimālo izmēru, ko var parādīt.' - text_custom_field_possible_values_info: 'Katra vērtības savā rindā' - text_wiki_page_destroy_question: "Šij lapai ir %{descendants} apakšlapa(as) un pēcnācēji. Ko darīt?" - text_wiki_page_nullify_children: "Paturēt apakšlapas kā pamatlapas" - text_wiki_page_destroy_children: "Dzēst apakšlapas un visus pēcnācējus" - text_wiki_page_reassign_children: "Piešķirt apakšlapas šai lapai" - text_own_membership_delete_confirmation: "Jūs tūlīt dzēsīsiet dažas vai visas atļaujas, un Jums pēc tam var nebūt atļauja labot šo projektu.\nVai turpināt?" - - default_role_manager: Menedžeris - default_role_developer: Izstrādātājs - default_role_reporter: Ziņotājs - default_tracker_bug: Kļūda - default_tracker_feature: Iezīme - default_tracker_support: Atbalsts - default_issue_status_new: Jauns - default_issue_status_in_progress: Attīstībā - default_issue_status_resolved: Atrisināts - default_issue_status_feedback: Atsauksmes - default_issue_status_closed: Slēgts - default_issue_status_rejected: Noraidīts - default_doc_category_user: Lietotāja dokumentācija - default_doc_category_tech: Tehniskā dokumentācija - default_priority_low: Zema - default_priority_normal: Normāla - default_priority_high: Augsta - default_priority_urgent: Steidzama - default_priority_immediate: Tūlītēja - default_activity_design: Dizains - default_activity_development: Izstrādāšana - - enumeration_issue_priorities: Uzdevumu prioritātes - enumeration_doc_categories: Dokumentu kategorijas - enumeration_activities: Aktivitātes (laika uzskaite) - enumeration_system_activity: Sistēmas aktivitātes - - error_can_not_delete_custom_field: Unable to delete custom field - permission_manage_subtasks: Manage subtasks - label_profile: Profile - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - field_parent_issue: Parent task - error_unable_delete_issue_status: Unable to delete issue status - label_subtask_plural: Subtasks - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - label_project_copy_notifications: Send email notifications during the project copy - 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_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: Kodēt ziņojumus - 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}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 uzdevums - one: 1 uzdevums - other: "%{count} uzdevumi" - 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: visi - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Ar apakšprojektiem - label_cross_project_tree: Ar projekta koku - label_cross_project_hierarchy: Ar projektu hierarhiju - label_cross_project_system: Ar visiem projektiem - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bb/bb4b688c8d8adb68413657f96f8e02f7400f5329.svn-base --- a/.svn/pristine/bb/bb4b688c8d8adb68413657f96f8e02f7400f5329.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -

    <%= l(:label_issue_plural) %>

    -<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %>
    -<% if @project %> -<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @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) %>
    -<% end %> -<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %>
    -<% end %> -<%= call_hook(:view_issues_sidebar_planning_bottom) %> - -<%= render_sidebar_queries %> -<%= call_hook(:view_issues_sidebar_queries_bottom) %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bb/bb834d6c2f5551779bce71506eba9e5321f661e3.svn-base --- a/.svn/pristine/bb/bb834d6c2f5551779bce71506eba9e5321f661e3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 teardown - Setting.clear_cache - end - - 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 - - def test_per_page_options_array_should_be_an_empty_array_when_setting_is_blank - with_settings :per_page_options => nil do - assert_equal [], Setting.per_page_options_array - end - - with_settings :per_page_options => '' do - assert_equal [], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_an_array_of_integers - with_settings :per_page_options => '10, 25, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_omit_non_numerial_values - with_settings :per_page_options => 'a, 25, 50' do - assert_equal [25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_sorted - with_settings :per_page_options => '25, 10, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bc30ce4931d78e6c793accaf2d15ef5a8dea2204.svn-base --- a/.svn/pristine/bc/bc30ce4931d78e6c793accaf2d15ef5a8dea2204.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,420 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'cgi' - -module Redmine - module Scm - module Adapters - class CommandFailed < StandardError #:nodoc: - end - - class AbstractAdapter #:nodoc: - - # raised if scm command exited with error, e.g. unknown revision. - class ScmCommandAborted < CommandFailed; end - - class << self - def client_command - "" - end - - def shell_quote_command - if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java' - client_command - else - shell_quote(client_command) - end - end - - # Returns the version of the scm client - # Eg: [1, 5, 0] or [] if unknown - def client_version - [] - end - - # Returns the version string of the scm client - # Eg: '1.5.0' or 'Unknown version' if unknown - def client_version_string - v = client_version || 'Unknown version' - v.is_a?(Array) ? v.join('.') : v.to_s - end - - # Returns true if the current client version is above - # or equals the given one - # If option is :unknown is set to true, it will return - # true if the client version is unknown - def client_version_above?(v, options={}) - ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) - end - - def client_available - true - end - - def shell_quote(str) - if Redmine::Platform.mswin? - '"' + str.gsub(/"/, '\\"') + '"' - else - "'" + str.gsub(/'/, "'\"'\"'") + "'" - end - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = url - @login = login if login && !login.empty? - @password = (password || "") if @login - @root_url = root_url.blank? ? retrieve_root_url : root_url - end - - def adapter_name - 'Abstract' - end - - def supports_cat? - true - end - - def supports_annotate? - respond_to?('annotate') - end - - def root_url - @root_url - end - - def url - @url - end - - def path_encoding - nil - end - - # get info about the svn repository - def info - return nil - end - - # Returns the entry identified by path and revision identifier - # or nil if entry doesn't exist in the repository - 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) - es ? es.detect {|e| e.name == search_name} : nil - end - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - def entries(path=nil, identifier=nil, options={}) - return nil - end - - def branches - return nil - end - - def tags - return nil - end - - def default_branch - return nil - end - - def properties(path, identifier=nil) - return nil - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - return nil - end - - def diff(path, identifier_from, identifier_to=nil) - return nil - end - - def cat(path, identifier=nil) - return nil - end - - def with_leading_slash(path) - path ||= '' - (path[0,1]!="/") ? "/#{path}" : path - end - - def with_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path : "#{path}/" - end - - def without_leading_slash(path) - path ||= '' - path.gsub(%r{^/+}, '') - end - - def without_trailling_slash(path) - path ||= '' - (path[-1,1] == "/") ? path[0..-2] : path - end - - def shell_quote(str) - self.class.shell_quote(str) - end - - private - def retrieve_root_url - info = self.info - info ? info.root_url : nil - end - - def target(path, sq=true) - path ||= '' - base = path.match(/^\//) ? root_url : url - str = "#{base}/#{path}".gsub(/[?<>\*]/, '') - if sq - str = shell_quote(str) - end - str - end - - def logger - self.class.logger - end - - def shellout(cmd, options = {}, &block) - self.class.shellout(cmd, options, &block) - end - - def self.logger - Rails.logger - 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)}" - end - begin - mode = "r+" - IO.popen(cmd, mode) do |io| - io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding) - io.close_write unless options[:write_stdin] - block.call(io) if block_given? - end - ## If scm command does not exist, - ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException - ## in production environment. - # rescue Errno::ENOENT => e - rescue Exception => e - msg = strip_credential(e.message) - # The command failed, log it and re-raise - logmsg = "SCM command failed, " - logmsg += "make sure that your SCM command (e.g. svn) is " - logmsg += "in PATH (#{ENV['PATH']})\n" - logmsg += "You can configure your scm commands in config/configuration.yml.\n" - logmsg += "#{strip_credential(cmd)}\n" - logmsg += "with: #{msg}" - logger.error(logmsg) - raise CommandFailed.new(msg) - end - end - - # Hides username/password in a given command - def self.strip_credential(cmd) - q = (Redmine::Platform.mswin? ? '"' : "'") - cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') - end - - def strip_credential(cmd) - self.class.strip_credential(cmd) - end - - def scm_iconv(to, from, str) - return nil if str.nil? - return str if to == from - if str.respond_to?(:force_encoding) - str.force_encoding(from) - begin - str.encode(to) - rescue Exception => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - else - begin - Iconv.conv(to, from, str) - rescue Iconv::Failure => err - logger.error("failed to convert from #{from} to #{to}. #{err}") - nil - end - end - end - - def parse_xml(xml) - if RUBY_PLATFORM == 'java' - xml = xml.sub(%r{<\?xml[^>]*\?>}, '') - end - ActiveSupport::XmlMini.parse(xml) - end - end - - class Entries < Array - def sort_by_name - dup.sort! {|x,y| - if x.kind == y.kind - x.name.to_s <=> y.name.to_s - else - x.kind <=> y.kind - end - } - end - - def revisions - revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) - end - end - - class Info - attr_accessor :root_url, :lastrev - def initialize(attributes={}) - self.root_url = attributes[:root_url] if attributes[:root_url] - self.lastrev = attributes[:lastrev] - end - end - - class Entry - attr_accessor :name, :path, :kind, :size, :lastrev, :changeset - - def initialize(attributes={}) - self.name = attributes[:name] if attributes[:name] - self.path = attributes[:path] if attributes[:path] - self.kind = attributes[:kind] if attributes[:kind] - self.size = attributes[:size].to_i if attributes[:size] - self.lastrev = attributes[:lastrev] - end - - def is_file? - 'file' == self.kind - end - - def is_dir? - 'dir' == self.kind - end - - def is_text? - Redmine::MimeType.is_type?('text', name) - end - - def author - if changeset - changeset.author.to_s - elsif lastrev - Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first) - end - end - end - - class Revisions < Array - def latest - sort {|x,y| - unless x.time.nil? or y.time.nil? - x.time <=> y.time - else - 0 - end - }.last - end - end - - class Revision - attr_accessor :scmid, :name, :author, :time, :message, - :paths, :revision, :branch, :identifier, - :parents - - def initialize(attributes={}) - self.identifier = attributes[:identifier] - self.scmid = attributes[:scmid] - self.name = attributes[:name] || self.identifier - self.author = attributes[:author] - self.time = attributes[:time] - self.message = attributes[:message] || "" - self.paths = attributes[:paths] - self.revision = attributes[:revision] - self.branch = attributes[:branch] - self.parents = attributes[:parents] - end - - # Returns the readable identifier. - def format_identifier - self.identifier.to_s - end - - def ==(other) - if other.nil? - false - elsif scmid.present? - scmid == other.scmid - elsif identifier.present? - identifier == other.identifier - elsif revision.present? - revision == other.revision - end - end - end - - class Annotate - attr_reader :lines, :revisions - - def initialize - @lines = [] - @revisions = [] - end - - def add_line(line, revision) - @lines << line - @revisions << revision - end - - def content - content = lines.join("\n") - end - - def empty? - lines.empty? - end - end - - class Branch < String - attr_accessor :revision, :scmid - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bc5cd9a0ce7e6dce66f1e1a15e9e36c9090b2ffd.svn-base --- a/.svn/pristine/bc/bc5cd9a0ce7e6dce66f1e1a15e9e36c9090b2ffd.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

    <%= l(:label_calendar) %>

    - -<% calendar = Redmine::Helpers::Calendar.new(Date.today, current_language, :week) - calendar.events = Issue.visible.find :all, - :conditions => ["#{Issue.table_name}.project_id in (#{@user.projects.collect{|m| m.id}.join(',')}) AND ((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", calendar.startdt, calendar.enddt, calendar.startdt, calendar.enddt], - :include => [:project, :tracker, :priority, :assigned_to] unless @user.projects.empty? %> - -<%= render :partial => 'common/calendar', :locals => {:calendar => calendar } %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bc78f131f015345e2b1b17598e4635974e716b9c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bc/bc78f131f015345e2b1b17598e4635974e716b9c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, :enabled_modules + + 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/1/archive" + 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/1/unarchive" + assert_redirected_to "/admin/projects" + assert Project.find(1).active? + get "projects/1" + assert_response :success + end + + def test_modules_should_not_allow_get + assert_no_difference 'EnabledModule.count' do + get '/projects/1/modules', {:enabled_module_names => ['']}, credentials('jsmith') + assert_response 404 + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bc9185ee675b6fb8b2207370c1cf95af6aa786d9.svn-base --- a/.svn/pristine/bc/bc9185ee675b6fb8b2207370c1cf95af6aa786d9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -

    <%=l(:label_document_plural)%>

    - -<% project_ids = @user.projects.select {|p| @user.allowed_to?(:view_documents, p)}.collect(&:id) %> -<%= render(:partial => 'documents/document', - :collection => Document.find(:all, - :limit => 10, - :order => "#{Document.table_name}.created_on DESC", - :conditions => "#{Document.table_name}.project_id in (#{project_ids.join(',')})", - :include => [:project])) unless project_ids.empty? %> \ No newline at end of file diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bcd855d1f37773ea6cbe38e91cc6db056d7aef2e.svn-base --- a/.svn/pristine/bc/bcd855d1f37773ea6cbe38e91cc6db056d7aef2e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -
    -<%= link_to(l(:button_edit), edit_user_path(@user), :class => 'icon icon-edit') if User.current.admin? %> -
    - -

    <%= avatar @user, :size => "50" %> <%=h @user.name %>

    - -
    -
      - <% unless @user.pref.hide_mail %> -
    • <%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %>
    • - <% end %> - <% @user.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 %> -
    • <%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %>
    • - <% unless @user.last_login_on.nil? %> -
    • <%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %>
    • - <% end %> -
    - -<% unless @memberships.empty? %> -

    <%=l(:label_project_plural)%>

    -
      -<% for membership in @memberships %> -
    • <%= link_to_project(membership.project) %> - (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)
    • -<% end %> -
    -<% end %> -<%= call_hook :view_account_left_bottom, :user => @user %> -
    - -
    - -<% unless @events_by_day.empty? %> -

    <%= link_to l(:label_activity), :controller => 'activities', - :action => 'index', :id => nil, :user_id => @user, - :from => @events_by_day.keys.first %>

    - -

    -<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> -

    - -
    -<% @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| -%> -
    - <%= format_time(e.event_datetime, false) %> - <%= content_tag('span', h(e.project), :class => 'project') %> - <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    <%= format_activity_description(e.event_description) %>
    -<% end -%> -
    -<% end -%> -
    - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :key => User.current.rss_key} %> -<% end %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> -<% end %> -<% end %> -<%= call_hook :view_account_right_bottom, :user => @user %> -
    - -<% html_title @user.name %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bcefb9a2bbe2d9c2d605eef1915f4d25e465456c.svn-base --- a/.svn/pristine/bc/bcefb9a2bbe2d9c2d605eef1915f4d25e465456c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,772 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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' -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.find :first, :conditions => { :is_closed => true } - 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.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') - 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.find(: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.find(: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.find(:all).each do |milestone| - print '.' - STDOUT.flush - # First we try to find the wiki page... - p = wiki.find_or_new_page(milestone.name.to_s) - p.content = WikiContent.new(:page => p) if p.new_record? - p.content.text = milestone.description.to_s - p.content.author = find_or_create_user('trac') - p.content.comments = 'Milestone' - p.save - - v = Version.new :project => @target_project, - :name => encode(milestone.name[0, limit_for(Version, 'name')]), - :description => 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.find(:all) - f.projects << @target_project - custom_field_map[field.name] = f - end - puts - - # Trac 'resolution' field as a Redmine custom field - r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) - r = IssueCustomField.new(:name => 'Resolution', - :field_format => 'list', - :is_filter => true) if r.nil? - r.trackers = Tracker.find(: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.find(:all, :order => 'name, version').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) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - puts "Invalid encoding!" - return false - end - - def self.set_trac_directory(path) - @@trac_directory = path - raise "This directory doesn't exist!" unless File.directory?(path) - 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 - - private - 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: 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bc/bcfa9d438ab075834f07a24d5350ce45aa5386bc.svn-base --- a/.svn/pristine/bc/bcfa9d438ab075834f07a24d5350ce45aa5386bc.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - include ERB::Util - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bd122c892e2cdfabeea5c22cd41a607729e832fd.svn-base --- a/.svn/pristine/bd/bd122c892e2cdfabeea5c22cd41a607729e832fd.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingReportsTest < ActionController::IntegrationTest - def test_reports - assert_routing( - { :method => 'get', :path => "/projects/567/issues/report" }, - { :controller => 'reports', :action => 'issue_report', :id => '567' } - ) - assert_routing( - { :method => 'get', :path => "/projects/567/issues/report/assigned_to" }, - { :controller => 'reports', :action => 'issue_report_details', - :id => '567', :detail => 'assigned_to' } - ) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bd1a4c5eaed10d182ad04396f746e4bc87bd2e06.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bd/bd1a4c5eaed10d182ad04396f746e4bc87bd2e06.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,45 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bd20515ba5f4454bba8c775f551e735dca61a27d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bd/bd20515ba5f4454bba8c775f551e735dca61a27d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,870 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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] + @inline = options.key?(:inline) ? options[:inline] : true + @caption_key = options[:caption] || "field_#{name}".to_sym + @frozen = options[:frozen] + end + + def caption + @caption_key.is_a?(Symbol) ? l(@caption_key) : @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 inline? + @inline + end + + def frozen? + @frozen + end + + def value(object) + object.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 + self.groupable = custom_field.group_statement || false + @inline = true + @cf = custom_field + end + + def caption + @cf.name + end + + def custom_field + @cf + end + + def value(object) + if custom_field.visible_by?(object.project, User.current) + 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 + else + nil + end + end + + def css_classes + @css_classes ||= "#{name} #{@cf.field_format}" + 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 + + VISIBILITY_PRIVATE = 0 + VISIBILITY_ROLES = 1 + VISIBILITY_PUBLIC = 2 + + belongs_to :project + belongs_to :user + has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id" + serialize :filters + serialize :column_names + serialize :sort_criteria, Array + serialize :options, Hash + + attr_protected :project_id, :user_id + + validates_presence_of :name + validates_length_of :name, :maximum => 255 + validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] } + validate :validate_query_filters + validate do |query| + errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank? + end + + after_save do |query| + if query.visibility_changed? && query.visibility != VISIBILITY_ROLES + query.roles.clear + end + end + + 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 + } + + 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", "!*", "*"] + } + + class_attribute :available_columns + self.available_columns = [] + + class_attribute :queried_class + + def queried_table_name + @queried_table_name ||= self.class.queried_class.table_name + end + + def initialize(attributes=nil, *args) + super attributes + @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) + case type_for(field) + when :integer + add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) } + when :float + add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) } + when :date, :date_past + case operator_for(field) + when "=", ">=", "<=", "><" + add_filter_error(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-", "t+", " 'activerecord.errors.messages') + errors.add(:base, m) + end + + def editable_by?(user) + return false unless user + # Admin can edit them all and regular users can edit their private queries + return true if user.admin? || (is_private? && self.user_id == user.id) + # Members can not edit public queries that are for all project (only admin is allowed to) + is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project) + end + + def 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 + + # Returns a representation of the available filters for JSON serialization + def available_filters_as_json + json = {} + available_filters.each do |field, options| + json[field] = options.slice(:type, :name, :values).stringify_keys + end + json + end + + def all_projects + @all_projects ||= Project.visible.all + end + + def all_projects_values + return @all_projects_values if @all_projects_values + + values = [] + Project.project_tree(all_projects) do |p, level| + prefix = (level > 0 ? ('--' * level + ' ') : '') + values << ["#{prefix}#{p.name}", p.id.to_s] + end + @all_projects_values = values + end + + # 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] + 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)}(.*)$/ + values = $1 + add_filter field, operator, values.present? ? values.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 ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) + 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 + available_columns.inject({}) {|h, column| + h[column.name.to_s] = column.sortable + h + } + end + + def columns + # preserve the column_names order + 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 + columns.select(&:inline?) + end + + def block_columns + columns.reject(&:inline?) + end + + def available_inline_columns + available_columns.select(&:inline?) + end + + def available_block_columns + available_columns.reject(&:inline?) + end + + def default_columns_names + [] + 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.is_a?(QueryColumn) ? column.name : column) + end + + def has_custom_field_column? + columns.any? {|column| column.is_a? QueryCustomFieldColumn} + 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 == false) ? 'desc' : '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 + + def sort_criteria_order_for(key) + sort_criteria.detect {|k, order| key.to_s == k}.try(:last) + end + + # Returns the SQL sort order that should be prepended for grouping + def group_by_sort_order + if grouped? && (column = group_by_column) + order = sort_criteria_order_for(column.name) || column.default_order + column.sortable.is_a?(Array) ? + column.sortable.collect {|s| "#{s} #{order}"}.join(',') : + "#{column.sortable} #{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 user_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 == 'project_id' + if v.delete('mine') + v += User.current.memberships.map(&:project_id).map(&:to_s) + 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, queried_table_name, field) + ')' + end + end if filters and valid? + + if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn) + # Excludes results for which the grouped custom field is not visible + filters_clauses << c.custom_field.visibility_by_project_condition + end + + filters_clauses << project_statement + filters_clauses.reject!(&:blank?) + + filters_clauses.any? ? filters_clauses.join(' AND ') : nil + end + + private + + def sql_for_custom_field(field, operator, value, custom_field_id) + db_table = CustomValue.table_name + db_field = 'value' + filter = @available_filters[field] + return nil unless filter + if filter[:format] == 'user' + if value.delete('me') + value.push User.current.id.to_s + end + end + not_in = nil + if operator == '!' + # Makes ! operator work for custom fields with multiple values + operator = '=' + not_in = 'NOT' + end + customized_key = "id" + customized_class = queried_class + if field =~ /^(.+)\.cf_/ + assoc = $1 + customized_key = "#{assoc}_id" + 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 + 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}) AND (#{filter[:field].visibility_by_project_condition}))" + 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 + if is_custom_filter + 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(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 + 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 = "(#{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 + 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 = "(#{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 + 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 = "(#{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 = "#{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 = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" + when ">t-" + # >= today - n days + sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil) + when "t+" + # >= today + n days + 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 "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 "!~" + 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 + + # Adds a filter for the given custom field + def add_custom_field_filter(field, assoc=nil) + case field.field_format + when "text" + options = { :type => :text } + when "list" + options = { :type => :list_optional, :values => field.possible_values } + when "date" + options = { :type => :date } + when "bool" + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] } + when "int" + options = { :type => :integer } + when "float" + options = { :type => :float } + when "user", "version" + return 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 } + else + options = { :type => :string } + end + filter_id = "cf_#{field.id}" + filter_name = field.name + if assoc.present? + filter_id = "#{assoc}.#{filter_id}" + filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) + end + add_available_filter filter_id, options.merge({ + :name => filter_name, + :format => field.field_format, + :field => field + }) + end + + # Adds filters for the given custom fields scope + def add_custom_fields_filters(scope, assoc=nil) + scope.visible.where(:is_filter => true).sorted.each do |field| + add_custom_field_filter(field, assoc) + end + end + + # Adds filters for the given associations custom fields + def add_associations_custom_fields_filters(*associations) + fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class) + associations.each do |assoc| + association_klass = queried_class.reflect_on_association(assoc).klass + fields_by_class.each do |field_class, fields| + if field_class.customized_class <= association_klass + fields.sort.each do |field| + add_custom_field_filter(field, assoc) + end + end + end + 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_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day) + if self.class.default_timezone == :utc + from_yesterday_time = from_yesterday_time.utc + end + s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)]) + end + if to + to_time = Time.local(to.year, to.month, to.day) + if self.class.default_timezone == :utc + to_time = to_time.utc + end + s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.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 + + # Additional joins required for the given sort options + def joins_for_order_statement(order_options) + joins = [] + + if order_options + if order_options.include?('authors') + 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} + join = column && column.custom_field.join_for_order_statement + if join + joins << join + end + end + end + + joins.any? ? joins.join(' ') : nil + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bd28ebed5ff75e171d4be4be4a90cc87c79d9d52.svn-base --- a/.svn/pristine/bd/bd28ebed5ff75e171d4be4be4a90cc87c79d9d52.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -<%= form_tag({}) do -%> -<%= hidden_field_tag 'back_url', url_for(params), :id => nil %> -
    - - - - - <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> - <% 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) %><%= link_to issue.id, issue_path(issue) %>#{column_content(column, issue)}
    <%= text %>
    -
    -<% end -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bd3a97351347227e1d7fd377fed6a7d933544ec4.svn-base --- a/.svn/pristine/bd/bd3a97351347227e1d7fd377fed6a7d933544ec4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 TimelogHelperTest < ActionView::TestCase - include TimelogHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::DateHelper - include ERB::Util - - fixtures :projects, :roles, :enabled_modules, :users, - :repositories, :changesets, - :trackers, :issue_statuses, :issues, :versions, :documents, - :wikis, :wiki_pages, :wiki_contents, - :boards, :messages, - :attachments, - :enumerations - - def setup - super - end - - def test_activities_collection_for_select_options_should_return_array_of_activity_names_and_ids - activities = activity_collection_for_select_options - assert activities.include?(["Design", 9]) - assert activities.include?(["Development", 10]) - end - - def test_activities_collection_for_select_options_should_not_include_inactive_activities - activities = activity_collection_for_select_options - assert !activities.include?(["Inactive Activity", 14]) - end - - def test_activities_collection_for_select_options_should_use_the_projects_override - project = Project.find(1) - override_activity = TimeEntryActivity.create!({:name => "Design override", :parent => TimeEntryActivity.find_by_name("Design"), :project => project}) - - activities = activity_collection_for_select_options(nil, project) - assert !activities.include?(["Design", 9]), "System activity found in: " + activities.inspect - assert activities.include?(["Design override", override_activity.id]), "Override activity not found in: " + activities.inspect - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bdc3036c84c5c2b90538770a9b1b1626e59b6d16.svn-base --- a/.svn/pristine/bd/bdc3036c84c5c2b90538770a9b1b1626e59b6d16.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bd/bde53e0adc79ce39deba6abf485a848292107754.svn-base --- a/.svn/pristine/bd/bde53e0adc79ce39deba6abf485a848292107754.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %> - -<% details_to_strings(@journal.details, true).each do |string| -%> -<%= string %> -<% end -%> - -<% if @journal.notes? -%> -<%= @journal.notes %> - -<% end -%> ----------------------------------------- -<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/be06c1fdad33ad10b8d30c53349e2482e780c5fc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/be06c1fdad33ad10b8d30c53349e2482e780c5fc.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,159 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/mercurial_adapter' + +class Repository::Mercurial < Repository + # sort changesets by revision number + has_many :changesets, + :order => "#{Changeset.table_name}.id DESC", + :foreign_key => 'repository_id' + + attr_protected :root_url + validates_presence_of :url + + # number of changesets to fetch at once + FETCH_AT_ONCE = 100 + + 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::MercurialAdapter + end + + def self.scm_name + 'Mercurial' + end + + def supports_directory_revisions? + true + end + + def supports_revision_graph? + true + end + + def repo_log_encoding + 'UTF-8' + end + + # Returns the readable identifier for the given mercurial changeset + def self.format_changeset_identifier(changeset) + "#{changeset.revision}:#{changeset.scmid}" + end + + # Returns the identifier for the given Mercurial changeset + def self.changeset_identifier(changeset) + changeset.scmid + end + + def diff_format_revisions(cs, cs_to, sep=':') + super(cs, cs_to, ' ') + 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? + s = name.to_s + if /[^\d]/ =~ s or s.size > 8 + cs = changesets.where(:scmid => s).first + else + cs = changesets.where(:revision => s).first + end + return cs if cs + changesets.where('scmid LIKE ?', "#{s}%").first + end + + # Returns the latest changesets for +path+; sorted by revision number + # + # Because :order => 'id DESC' is defined at 'has_many', + # there is no need to set 'order'. + # But, MySQL test fails. + # Sqlite3 and PostgreSQL pass. + # Is this MySQL bug? + def latest_changesets(path, rev, limit=10) + 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) + cond, args = [], [] + if scm.branchmap.member? rev + # Mercurial named branch is *stable* in each revision. + # So, named branch can be stored in database. + # Mercurial provides *bookmark* which is equivalent with git branch. + # But, bookmark is not implemented. + cond << "#{Changeset.table_name}.scmid IN (?)" + # Revisions in root directory and sub directory are not equal. + # So, in order to get correct limit, we need to get all revisions. + # But, it is very heavy. + # Mercurial does not treat direcotry. + # So, "hg log DIR" is very heavy. + branch_limit = path.blank? ? limit : ( limit * 5 ) + args << scm.nodes_in_branch(rev, :limit => branch_limit) + elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil + cond << "#{Changeset.table_name}.id <= ?" + args << last.id + end + unless path.blank? + cond << "EXISTS (SELECT * FROM #{Change.table_name} + WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id + AND (#{Change.table_name}.path = ? + OR #{Change.table_name}.path LIKE ? ESCAPE ?))" + args << path.with_leading_slash + args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\' + end + [cond.join(' AND '), *args] unless cond.empty? + end + private :latest_changesets_cond + + def fetch_changesets + return if scm.info.nil? + scm_rev = scm.info.lastrev.revision.to_i + db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 + return unless db_rev < scm_rev # already up-to-date + + logger.debug "Fetching changesets for repository #{url}" if logger + (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| + scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| + transaction do + parents = (re.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact + cs = Changeset.create(:repository => self, + :revision => re.revision, + :scmid => re.scmid, + :committer => re.author, + :committed_on => re.time, + :comments => re.message, + :parents => parents) + unless cs.new_record? + re.paths.each { |e| cs.create_change(e) } + end + end + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/be4b32c2aa3919bf1459ac5ceda1f30275086d1d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/be4b32c2aa3919bf1459ac5ceda1f30275086d1d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,172 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + test "GET /projects/:project_id/memberships.xml 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 + + test "GET /projects/:project_id/memberships.json 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 + + test "POST /projects/:project_id/memberships.xml should create the 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 + + test "POST /projects/:project_id/memberships.xml with invalid parameters should return errors" 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 + + test "GET /memberships/:id.xml 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 + + test "GET /memberships/:id.json 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 + + test "PUT /memberships/:id.xml should update the 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 + + test "PUT /memberships/:id.xml with invalid parameters should return errors" 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 + + test "DELETE /memberships/:id.xml should destroy the 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 + + test "DELETE /memberships/:id.xml 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/be5eea4d65ddd58278f59654aa0a6ad304fac616.svn-base --- a/.svn/pristine/be/be5eea4d65ddd58278f59654aa0a6ad304fac616.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -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.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/be60b294fa53596894bfd4efd7ed28e8a1bbb00b.svn-base --- a/.svn/pristine/be/be60b294fa53596894bfd4efd7ed28e8a1bbb00b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,252 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - fixtures :projects, - :trackers, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :journals, :journal_details, - :versions, - :issues, :issue_statuses, :issue_categories, - :users, - :enumerations, - :time_entries - - def test_context_menu_one_issue - @request.session[:user_id] = 2 - get :issues, :ids => [1] - assert_response :success - assert_template 'context_menu' - - 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_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 - 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).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - - 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 - @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).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - - 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 - field = IssueCustomField.create!(:name => 'List', :field_format => 'list', - :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3]) - @request.session[:user_id] = 2 - get :issues, :ids => [1] - - 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 - field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list', - :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3]) - @request.session[:user_id] = 2 - get :issues, :ids => [1, 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 - field = IssueCustomField.create!(:name => 'List', :field_format => 'list', - :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3]) - issue = Issue.find(1) - issue.custom_field_values = {field.id => 'Bar'} - issue.save! - @request.session[:user_id] = 2 - get :issues, :ids => [1] - - 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 - field = IssueCustomField.create!(:name => 'Bool', :field_format => 'bool', - :is_for_all => true, :tracker_ids => [1, 2, 3]) - @request.session[:user_id] = 2 - get :issues, :ids => [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 - field = IssueCustomField.create!(:name => 'User', :field_format => 'user', - :is_for_all => true, :tracker_ids => [1, 2, 3]) - @request.session[:user_id] = 2 - get :issues, :ids => [1] - - 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 - field = IssueCustomField.create!(:name => 'Version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1, 2, 3]) - @request.session[:user_id] = 2 - get :issues, :ids => [1] - - 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 - @request.session[:user_id] = 2 - get :issues, :ids => [1] - assert_response :success - assert_template 'context_menu' - - 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 - @request.session[:user_id] = 2 - version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1) - - get :issues, :ids => [1, 4] - assert_response :success - assert_template 'context_menu' - - assert_include version, assigns(:versions) - assert_select 'a', :text => 'eCookbook - Shared' - 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_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_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 - - get :time_entries, :ids => [1, 2] - assert_response :success - assert_template 'time_entries' - assert_select 'a.disabled', :text => 'Edit' - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/be8e6e4ab944ad41015bb9c64ecc1842cecaa059.svn-base --- a/.svn/pristine/be/be8e6e4ab944ad41015bb9c64ecc1842cecaa059.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 :action => 'edit', :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 :action => 'permissions', :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.nil? || @target_roles.nil? - 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 - end - end - end - - private - - def find_roles - @roles = Role.sorted.all - end - - def find_trackers - @trackers = Tracker.sorted.all - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/bedd1a4658313e01edaa3354b41ccf9ebbf924c3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/be/bedd1a4658313e01edaa3354b41ccf9ebbf924c3.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/be/beec6fa05b5b05e4e9b382e6432c329fa2ace5b9.svn-base --- a/.svn/pristine/be/beec6fa05b5b05e4e9b382e6432c329fa2ace5b9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,326 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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)") if logger - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bf27e7a4eaace3c336ad2614552c89b7687ffee1.svn-base --- a/.svn/pristine/bf/bf27e7a4eaace3c336ad2614552c89b7687ffee1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -

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

    -<% -entries = timelog_items -entries_by_day = entries.group_by(&:spent_on) -%> - -
    -

    <%= l(:label_total_time) %>: <%= 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 %> <%= h(' - ') + 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}, - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> - <% end -%> -
    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bf2a0b085dae832659996e0ba149d09caa255a18.svn-base Binary file .svn/pristine/bf/bf2a0b085dae832659996e0ba149d09caa255a18.svn-base has changed diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bf4f93978948907accea2adbd188dc8d3101593b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bf4f93978948907accea2adbd188dc8d3101593b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,142 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::TimeEntriesTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :time_entries + + def setup + Setting.rest_api_enabled = '1' + end + + test "GET /time_entries.xml should return time entries" do + get '/time_entries.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}} + end + + test "GET /time_entries.xml with limit should return limited results" do + get '/time_entries.xml?limit=2', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entries', + :children => {:count => 2} + end + + test "GET /time_entries/:id.xml should return the time entry" do + get '/time_entries/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'time_entry', + :child => {:tag => 'id', :content => '2'} + end + + test "POST /time_entries.xml with issue_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_equal Issue.find(1), entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity + end + + test "POST /time_entries.xml with issue_id should accept custom fields" do + field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string') + + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => { + :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}] + }}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'accepted', entry.custom_field_value(field) + end + + test "POST /time_entries.xml with project_id should create time entry" do + assert_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + entry = TimeEntry.first(:order => 'id DESC') + assert_equal 'jsmith', entry.user.login + assert_nil entry.issue + assert_equal Project.find(1), entry.project + assert_equal Date.parse('2010-12-02'), entry.spent_on + assert_equal 3.5, entry.hours + assert_equal TimeEntryActivity.find(11), entry.activity + end + + test "POST /time_entries.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "PUT /time_entries/:id.xml with valid parameters should update time entry" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', TimeEntry.find(2).comments + end + + test "PUT /time_entries/:id.xml with invalid parameters should return errors" do + assert_no_difference 'TimeEntry.count' do + put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} + end + + test "DELETE /time_entries/:id.xml should destroy time entry" do + assert_difference 'TimeEntry.count', -1 do + delete '/time_entries/2.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil TimeEntry.find_by_id(2) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bf704ffc85732314f4254ede294fe941c370ac2e.svn-base --- a/.svn/pristine/bf/bf704ffc85732314f4254ede294fe941c370ac2e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bfe4ef3580246b72914ef6e2a0108f1ec79d744c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/bf/bfe4ef3580246b72914ef6e2a0108f1ec79d744c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,297 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class TimelogController < ApplicationController + menu_item :issues + + before_filter :find_project_for_new_time_entry, :only => [:create] + before_filter :find_time_entry, :only => [:show, :edit, :update] + before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy] + before_filter :authorize, :except => [:new, :index, :report] + + before_filter :find_optional_project, :only => [:index, :report] + before_filter :find_optional_project_for_new_time_entry, :only => [:new] + before_filter :authorize_global, :only => [:new, :index, :report] + + 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 + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + + sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + scope = time_entry_scope(:order => sort_clause). + includes(:project, :user, :issue). + preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]) + + respond_to do |format| + format.html { + @entry_count = scope.count + @entry_pages = Paginator.new @entry_count, per_page_option, params['page'] + @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).all + @total_hours = scope.sum(:hours).to_f + + render :layout => !request.xhr? + } + format.api { + @entry_count = scope.count + @offset, @limit = api_offset_and_limit + @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).all + } + format.atom { + entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").all + render_feed(entries, :title => l(:label_spent_time)) + } + format.csv { + # Export all entries + @entries = scope.all + send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') + } + end + end + + def report + @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? } + format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') } + end + end + + def show + respond_to do |format| + # TODO: Implement html response + format.html { render :nothing => true, :status => 406 } + format.api + end + end + + def new + @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) + @time_entry.safe_attributes = params[:time_entry] + end + + def create + @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today) + @time_entry.safe_attributes = params[:time_entry] + + call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) + + if @time_entry.save + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_create) + if params[:continue] + if params[:project_id] + 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 + 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 project_time_entries_path(@time_entry.project) + end + } + format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } + end + else + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@time_entry) } + end + end + end + + def edit + @time_entry.safe_attributes = params[:time_entry] + end + + def update + @time_entry.safe_attributes = params[:time_entry] + + call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry }) + + if @time_entry.save + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_back_or_default project_time_entries_path(@time_entry.project) + } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@time_entry) } + end + end + end + + def bulk_edit + @available_activities = TimeEntryActivity.shared.active + @custom_fields = TimeEntry.first.available_custom_fields + end + + def bulk_update + attributes = parse_params_for_bulk_time_entry_attributes(params) + + unsaved_time_entry_ids = [] + @time_entries.each do |time_entry| + time_entry.reload + time_entry.safe_attributes = attributes + call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry }) + unless time_entry.save + logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info + # Keep unsaved time_entry ids to display them in flash error + unsaved_time_entry_ids << time_entry.id + end + end + set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) + redirect_back_or_default project_time_entries_path(@projects.first) + end + + def destroy + destroyed = TimeEntry.transaction do + @time_entries.each do |t| + unless t.destroy && t.destroyed? + raise ActiveRecord::Rollback + end + end + end + + respond_to do |format| + format.html { + if destroyed + flash[:notice] = l(:notice_successful_delete) + else + flash[:error] = l(:notice_unable_delete_time_entry) + end + redirect_back_or_default project_time_entries_path(@projects.first) + } + format.api { + if destroyed + render_api_ok + else + render_validation_errors(@time_entries) + end + } + end + end + +private + def find_time_entry + @time_entry = TimeEntry.find(params[:id]) + unless @time_entry.editable_by?(User.current) + render_403 + return false + end + @project = @time_entry.project + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_time_entries + @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids]) + raise ActiveRecord::RecordNotFound if @time_entries.empty? + @projects = @time_entries.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 + rescue ActiveRecord::RecordNotFound + render_404 + end + + def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids) + if unsaved_time_entry_ids.empty? + flash[:notice] = l(:notice_successful_update) unless time_entries.empty? + else + flash[:error] = l(:notice_failed_to_save_time_entries, + :count => unsaved_time_entry_ids.size, + :total => time_entries.size, + :ids => '#' + unsaved_time_entry_ids.join(', #')) + end + end + + def find_optional_project_for_new_time_entry + if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present? + @project = Project.find(project_id) + end + if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? + @issue = Issue.find(issue_id) + @project ||= @issue.project + end + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_project_for_new_time_entry + find_optional_project_for_new_time_entry + if @project.nil? + render_404 + end + end + + def find_optional_project + if !params[:issue_id].blank? + @issue = Issue.find(params[:issue_id]) + @project = @issue.project + elsif !params[:project_id].blank? + @project = Project.find(params[:project_id]) + end + end + + # Returns the TimeEntry scope for index and report actions + def time_entry_scope(options={}) + scope = @query.results_scope(options) + if @issue + scope = scope.on_issue(@issue) + end + scope + end + + def parse_params_for_bulk_time_entry_attributes(params) + attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] + attributes + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/bf/bffc97455f7192a4c50a5daa226945f5e048b46b.svn-base --- a/.svn/pristine/bf/bffc97455f7192a4c50a5daa226945f5e048b46b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -desc "Run the Continous Integration tests for Redmine" -task :ci do - # RAILS_ENV and ENV[] can diverge so force them both to test - ENV['RAILS_ENV'] = 'test' - RAILS_ENV = 'test' - Rake::Task["ci:setup"].invoke - Rake::Task["ci:build"].invoke - Rake::Task["ci:teardown"].invoke -end - -namespace :ci do - desc "Setup Redmine for a new build" - task :setup do - Rake::Task["tmp:clear"].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 - - desc "Finish the build" - task :teardown do - end -end - -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' - 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 - - File.open('config/database.yml', 'w') do |f| - f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c01563d0a237a5d88091f0f7a0b811b886e08b5f.svn-base --- a/.svn/pristine/c0/c01563d0a237a5d88091f0f7a0b811b886e08b5f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1102 +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_hours: - one: "1 sahat" - other: "%{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' - negative_format: '%u -%n' - 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: "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:" - - - 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_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_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_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_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}" - 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: '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 - 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 aktivnost - one: 1 aktivnost - other: "%{count} aktivnosti" - 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: sve - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c05debcab8da4ac1aa187ec71f9925ef9b282967.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c05debcab8da4ac1aa187ec71f9925ef9b282967.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,345 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 WorkflowsControllerTest < ActionController::TestCase + fixtures :roles, :trackers, :workflows, :users, :issue_statuses + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_response :success + assert_template 'index' + + count = WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count + 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 + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) + WorkflowTransition.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 + WorkflowTransition.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, 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 + 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, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count + + 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.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first + assert w.author + assert ! w.assignee + 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.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first + assert w.author + assert w.assignee + end + + def test_clear_workflow + assert WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count > 0 + + post :edit, :role_id => 1, :tracker_id => 2 + assert_equal 0, WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count + end + + def test_get_permissions + get :permissions + + assert_response :success + assert_template 'permissions' + assert_not_nil assigns(:roles) + assert_not_nil assigns(:trackers) + end + + def test_get_permissions_with_role_and_tracker + WorkflowPermission.delete_all + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') + + get :permissions, :role_id => 1, :tracker_id => 2 + assert_response :success + assert_template 'permissions' + + assert_select 'input[name=role_id][value=1]' + assert_select 'input[name=tracker_id][value=2]' + + # Required field + assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]', 0 + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]' + end + + # Read-only field + assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]' + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]', 0 + end + + # Other field + assert_select 'select[name=?]', 'permissions[due_date][3]' do + assert_select 'option[value=]' + assert_select 'option[value=][selected=selected]', 0 + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=readonly][selected=selected]', 0 + assert_select 'option[value=required]', :text => 'Required' + assert_select 'option[value=required][selected=selected]', 0 + end + end + + def test_get_permissions_with_required_custom_field_should_not_show_required_option + cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) + + get :permissions, :role_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'permissions' + + # Custom field that is always required + # The default option is "(Required)" + assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do + assert_select 'option[value=]' + assert_select 'option[value=readonly]', :text => 'Read-only' + assert_select 'option[value=required]', 0 + end + end + + def test_get_permissions_should_disable_hidden_custom_fields + cf1 = IssueCustomField.generate!(:tracker_ids => [1], :visible => true) + cf2 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1]) + cf3 = IssueCustomField.generate!(:tracker_ids => [1], :visible => false, :role_ids => [1, 2]) + + get :permissions, :role_id => 2, :tracker_id => 1 + assert_response :success + assert_template 'permissions' + + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf1.id}][1]" + assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf3.id}][1]" + + assert_select 'select[name=?][disabled=disabled]', "permissions[#{cf2.id}][1]" do + assert_select 'option[value=][selected=selected]', :text => 'Hidden' + end + end + + def test_get_permissions_with_role_and_tracker_and_all_statuses + WorkflowTransition.delete_all + + get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0' + assert_response :success + assert_equal IssueStatus.sorted.all, assigns(:statuses) + end + + def test_post_permissions + WorkflowPermission.delete_all + + post :permissions, :role_id => 1, :tracker_id => 2, :permissions => { + 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''}, + 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''}, + 'due_date' => {'1' => '', '2' => '', '3' => ''}, + } + assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' + + workflows = WorkflowPermission.all + assert_equal 3, workflows.size + workflows.each do |workflow| + assert_equal 1, workflow.role_id + assert_equal 2, workflow.tracker_id + end + assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'assigned_to_id' && wf.rule == 'readonly'} + assert workflows.detect {|wf| wf.old_status_id == 1 && wf.field_name == 'fixed_version_id' && wf.rule == 'required'} + assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'} + end + + def test_post_permissions_should_clear_permissions + WorkflowPermission.delete_all + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required') + WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required') + wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly') + + post :permissions, :role_id => 1, :tracker_id => 2 + assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2' + + workflows = WorkflowPermission.all + assert_equal 2, workflows.size + assert wf1.reload + assert wf2.reload + end + + def test_get_copy + get :copy + assert_response :success + assert_template 'copy' + assert_select 'select[name=source_tracker_id]' do + assert_select 'option[value=1]', :text => 'Bug' + end + assert_select 'select[name=source_role_id]' do + assert_select 'option[value=2]', :text => 'Developer' + end + assert_select 'select[name=?]', 'target_tracker_ids[]' do + assert_select 'option[value=3]', :text => 'Support request' + end + assert_select 'select[name=?]', 'target_role_ids[]' do + assert_select 'option[value=1]', :text => 'Manager' + end + 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 + + 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. + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c066ebfaa53749d111d7ed293c5b04920a16ca0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c066ebfaa53749d111d7ed293c5b04920a16ca0e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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, Redmine::CodesetUtil.replace_invalid_utf8(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)], + [:text_convert_available, Redmine::Thumbnail.convert_available?] + ] + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c0a2bb74527f8e6c89c1cd62e5d34e73efd38922.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c0a2bb74527f8e6c89c1cd62e5d34e73efd38922.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::ApiTest < Redmine::ApiTest::Base + fixtures :users + + def setup + Setting.rest_api_enabled = '1' + end + + def test_api_should_work_with_protect_from_forgery + ActionController::Base.allow_forgery_protection = true + assert_difference('User.count') do + post '/users.xml', { + :user => { + :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', + :mail => 'foo@example.net', :password => 'secret123'} + }, + credentials('admin') + assert_response 201 + end + ensure + ActionController::Base.allow_forgery_protection = false + end +end \ No newline at end of file diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c0b43345f86fbda7707c9e88d3122baac3b53e87.svn-base --- a/.svn/pristine/c0/c0b43345f86fbda7707c9e88d3122baac3b53e87.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class MessagesController < ApplicationController - 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] - - helper :boards - helper :watchers - helper :attachments - include AttachmentsHelper - - REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE) - - # Show a topic and its replies - def show - page = params[:page] - # Find the page of the requested reply - if params[:r] && page.nil? - offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i]) - page = 1 + offset / REPLIES_PER_PAGE - end - - @reply_count = @topic.children.count - @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? - end - - # Create a new topic - def new - @message = Message.new - @message.author = User.current - @message.board = @board - @message.safe_attributes = params[:message] - if request.post? - @message.save_attachments(params[:attachments]) - if @message.save - call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) - render_attachment_warning_if_needed(@message) - redirect_to board_message_path(@board, @message) - end - end - end - - # Reply to a topic - def reply - @reply = Message.new - @reply.author = User.current - @reply.board = @board - @reply.safe_attributes = params[:reply] - @topic.children << @reply - if !@reply.new_record? - call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply}) - attachments = Attachment.attach_files(@reply, params[:attachments]) - render_attachment_warning_if_needed(@reply) - end - redirect_to board_message_path(@board, @topic, :r => @reply) - end - - # Edit a message - def edit - (render_403; return false) unless @message.editable_by?(User.current) - @message.safe_attributes = params[:message] - if request.post? && @message.save - attachments = Attachment.attach_files(@message, params[:attachments]) - render_attachment_warning_if_needed(@message) - flash[:notice] = l(:notice_successful_update) - @message.reload - redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id)) - end - end - - # Delete a messages - def destroy - (render_403; return false) unless @message.destroyable_by?(User.current) - r = @message.to_param - @message.destroy - if @message.parent - redirect_to board_message_path(@board, @message.parent, :r => r) - else - redirect_to project_board_path(@project, @board) - end - end - - def quote - @subject = @message.subject - @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:') - - @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> " - @content << @message.content.to_s.strip.gsub(%r{
    ((.|\s)*?)
    }m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" - end - - def preview - message = @board.messages.find_by_id(params[:id]) - @text = (params[:message] || params[:reply])[:content] - @previewed = message - render :partial => 'common/preview' - end - -private - def find_message - return unless find_board - @message = @board.messages.find(params[:id], :include => :parent) - @topic = @message.root - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_board - @board = Board.find(params[:board_id], :include => :project) - @project = @board.project - rescue ActiveRecord::RecordNotFound - render_404 - nil - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c0c6c069aeab7d8e5dbef5b1fe66cdd0f25891ef.svn-base --- a/.svn/pristine/c0/c0c6c069aeab7d8e5dbef5b1fe66cdd0f25891ef.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +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 %>

    -<% 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_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> -

    - <%= 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c0d36c34eac79fa2295c3fdedaa72256c805d292.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c0/c0d36c34eac79fa2295c3fdedaa72256c805d292.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,739 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, :generate_password + 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 :generate_password_if_needed, :update_hashed_password + before_destroy :remove_references_before_destroy + after_save :update_notified_project_ids + + 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 + @notified_projects_ids = nil + @notified_projects_ids_changed = false + @builtin_role = 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, active_only=true) + 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.check_password?(password) + return nil if !user.active? && active_only + 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.active? + 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 + + def must_change_password? + must_change_passwd? && change_password_allowed? + end + + def generate_password? + generate_password == '1' || generate_password == true + end + + # Generate and set a random password on given length + def random_password(length=40) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + chars -= %w(0 O 1 l) + password = '' + length.times {|i| password << chars[SecureRandom.random_number(chars.size)] } + 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) + @notified_projects_ids_changed = true + @notified_projects_ids = ids + end + + # Updates per project notifications (after_save callback) + def update_notified_project_ids + if @notified_projects_ids_changed + ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : []) + members.update_all(:mail_notification => false) + members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any? + end + end + private :update_notified_project_ids + + 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 + + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.non_member + 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 membership = membership(project) + roles = membership.roles + else + roles << builtin_role + 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 the user's 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', + 'notified_project_ids', + 'language', + 'custom_field_values', + 'custom_fields', + 'identity_url' + + safe_attributes 'status', + 'auth_source_id', + 'generate_password', + 'must_change_passwd', + :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 + return if password.blank? && generate_password? + # 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 + + def generate_password_if_needed + if generate_password? && auth_source.nil? + length = [Setting.password_min_length.to_i + 2, 10].max + random_password(length) + end + end + + # 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 visibility = ?', id, ::Query::VISIBILITY_PRIVATE] + ::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 + + # Returns the user's bult-in role + def builtin_role + @builtin_role ||= Role.anonymous + end + + def membership(*args) + nil + end + + def member_of?(*args) + false + end + + # Anonymous user can not be destroyed + def destroy + false + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c0/c0ff820f520e8207d7f4feecd9bcb5cb4d1da9b1.svn-base --- a/.svn/pristine/c0/c0ff820f520e8207d7f4feecd9bcb5cb4d1da9b1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -

    <%= link_to(h("#{issue.tracker.name} ##{issue.id}: #{issue.subject}"), issue_url) %>

    - -
      -
    • <%=l(:field_author)%>: <%=h issue.author %>
    • -
    • <%=l(:field_status)%>: <%=h issue.status %>
    • -
    • <%=l(:field_priority)%>: <%=h issue.priority %>
    • -
    • <%=l(:field_assigned_to)%>: <%=h issue.assigned_to %>
    • -
    • <%=l(:field_category)%>: <%=h issue.category %>
    • -
    • <%=l(:field_fixed_version)%>: <%=h issue.fixed_version %>
    • -<% issue.custom_field_values.each do |c| %> -
    • <%=h c.custom_field.name %>: <%=h show_value(c) %>
    • -<% end %> -
    - -<%= textilizable(issue, :description, :only_path => false) %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c1/c10e9a1142bb5d5518887fd1a88ba99ec4614876.svn-base --- a/.svn/pristine/c1/c10e9a1142bb5d5518887fd1a88ba99ec4614876.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -

    -<%= label_tag "user_mail_notification", l(:description_user_mail_notification), :class => "hidden-for-sighted" %> -<%= select_tag( - 'user[mail_notification]', - options_for_select( - user_mail_notification_options(@user), @user.mail_notification), - :onchange => 'if (this.value == "selected") {$("#notified-projects").show();} else {$("#notified-projects").hide();}' - ) %> -

    -<%= content_tag 'div', :id => 'notified-projects', :style => (@user.mail_notification == 'selected' ? '' : 'display:none;') do %> - <%= render_project_nested_lists(@user.projects) do |project| - content_tag('label', - check_box_tag( - 'notified_project_ids[]', - project.id, - @user.notified_projects_ids.include?(project.id) - ) + ' ' + h(project.name) - ) - end %> -

    <%= l(:text_user_mail_option) %>

    -<% end %> -

    - -

    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c1/c125a6277f7dc560bacace655471aa7685efdb87.svn-base --- a/.svn/pristine/c1/c125a6277f7dc560bacace655471aa7685efdb87.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -
    -<%= link_to l(:label_group_new), new_group_path, :class => 'icon icon-add' %> -
    - -

    <%= l(:label_group_plural) %>

    - -<% if @groups.any? %> - - - - - - - -<% @groups.each do |group| %> - - - - - -<% end %> -
    <%=l(:label_group)%><%=l(:label_user_plural)%>
    <%= link_to h(group), edit_group_path(group) %><%= group.users.size %><%= delete_link group %>
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c1/c1781c3758d8dc14f56517d75f2f59e2fba8ccc3.svn-base --- a/.svn/pristine/c1/c1781c3758d8dc14f56517d75f2f59e2fba8ccc3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c1/c1f9e2096ed8b684022346a547814bc032420b8c.svn-base --- a/.svn/pristine/c1/c1f9e2096ed8b684022346a547814bc032420b8c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,468 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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] ||= {} - - if @@handler_options[:allow_override].is_a?(String) - @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) - end - @@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) - - email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) - super(email) - end - - def logger - Rails.logger - end - - cattr_accessor :ignored_emails_headers - @@ignored_emails_headers = { - 'X-Auto-Response-Suppress' => 'oof', - 'Auto-Submitted' => /^auto-/ - } - - # 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 - if logger && logger.info - logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" - end - return false - end - # Ignore auto generated emails - self.class.ignored_emails_headers.each do |key, ignored_value| - value = email.header[key] - if value - value = value.to_s.downcase - if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value - if logger && logger.info - logger.info "MailHandler: ignoring email with #{key}:#{value} header" - end - return false - end - end - end - @user = User.find_by_mail(sender_email) if sender_email.present? - if @user && !@user.active? - if logger && logger.info - logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" - end - 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 - if @user - if logger && logger.info - logger.info "MailHandler: [#{@user.login}] account created" - end - Mailer.account_information(@user, @user.password).deliver - else - if logger && logger.error - logger.error "MailHandler: could not create account for [#{sender_email}]" - end - return false - end - else - # Default behaviour, emails from unknown users are ignored - if logger && logger.info - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" - end - 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 = cleaned_up_subject - 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, from_journal=nil) - issue = Issue.find_by_id(issue_id) - return unless issue - # check permission - unless @@handler_options[:no_permission_check] - unless user.allowed_to?(:add_issue_notes, issue.project) || - user.allowed_to?(:edit_issues, issue.project) - raise UnauthorizedAction - end - end - - # ignore CLI-supplied defaults for new issues - @@handler_options[:issue].clear - - journal = issue.init_journal(user) - if from_journal && from_journal.private_notes? - # If the received email was a reply to a private note, make the added note private - issue.private_notes = true - end - 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! - if logger && logger.info - logger.info "MailHandler: issue ##{issue.id} updated by #{user}" - end - 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, journal) - 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 => cleaned_up_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 - if logger && logger.info - logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" - end - 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.decoded, - :filename => attachment.filename, - :author => user, - :content_type => attachment.mime_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) - if user && user.language.present? - keys << l("field_#{attr}", :default => '', :locale => user.language) - end - if Setting.default_language.present? - keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) - end - end - keys.reject! {|k| k.blank?} - keys.collect! {|k| Regexp.escape(k)} - format ||= '.+' - keyword = nil - regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i - if m = text.match(regexp) - keyword = m[2].strip - text.gsub!(regexp, '') - end - keyword - 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 keyword = get_keyword(v.custom_field.name, :override => true) - h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) - 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? - - part = email.text_part || email.html_part || email - @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset) - - # strip html tags and remove doctype directive - @plain_text_body = strip_tags(@plain_text_body.strip) - @plain_text_body.sub! %r{^$/) - addr, name = m[2], m[1] - end - if addr.present? - user = self.class.new_user_from_attributes(addr, name) - if user.save - user - else - logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger - nil - end - else - logger.error "MailHandler: failed to create User: no FROM address found" if logger - nil - 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)} - unless delimiters.empty? - regex = Regexp.new("^[> ]*(#{ 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.name.downcase == keyword} - end - assignee - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c2/c20e62b47c4a114de7fa73898ffad99a1fd633fb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c20e62b47c4a114de7fa73898ffad99a1fd633fb.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,124 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 MemberTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :groups_users, + :watchers, + :journals, :journal_details, + :messages, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :boards + + include Redmine::I18n + + def setup + @jsmith = Member.find(1) + end + + def test_create + member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2]) + assert member.save + member.reload + + assert_equal 2, member.roles.size + assert_equal Role.find(1), member.roles.sort.first + end + + def test_update + assert_equal "eCookbook", @jsmith.project.name + assert_equal "Manager", @jsmith.roles.first.name + assert_equal "jsmith", @jsmith.user.login + + @jsmith.mail_notification = !@jsmith.mail_notification + assert @jsmith.save + end + + def test_update_roles + assert_equal 1, @jsmith.roles.size + @jsmith.role_ids = [1, 2] + assert @jsmith.save + assert_equal 2, @jsmith.reload.roles.size + end + + def test_validate + member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2]) + # same use can't have more than one membership for a project + assert !member.save + + # must have one role at least + user = User.new(:firstname => "new1", :lastname => "user1", :mail => "test_validate@somenet.foo") + user.login = "test_validate" + user.password, user.password_confirmation = "password", "password" + assert user.save + + set_language_if_valid 'fr' + member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => []) + assert !member.save + assert_include I18n.translate('activerecord.errors.messages.empty'), member.errors[:role] + str = "R\xc3\xb4le doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + assert_equal str, [member.errors.full_messages].flatten.join + end + + def test_validate_member_role + user = User.new(:firstname => "new1", :lastname => "user1", :mail => "test_validate@somenet.foo") + user.login = "test_validate_member_role" + user.password, user.password_confirmation = "password", "password" + assert user.save + member = Member.new(:project_id => 1, :user_id => user.id, :role_ids => [5]) + assert !member.save + end + + def test_destroy + category1 = IssueCategory.find(1) + assert_equal @jsmith.user.id, category1.assigned_to_id + assert_difference 'Member.count', -1 do + assert_difference 'MemberRole.count', -1 do + @jsmith.destroy + end + end + assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) } + category1.reload + assert_nil category1.assigned_to_id + end + + def test_sort_without_roles + a = Member.new(:roles => [Role.first]) + b = Member.new + + assert_equal -1, a <=> b + assert_equal 1, b <=> a + end + + def test_sort_without_principal + role = Role.first + a = Member.new(:roles => [role], :principal => User.first) + b = Member.new(:roles => [role]) + + assert_equal -1, a <=> b + assert_equal 1, b <=> a + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c2/c2cb8c7008cd50c99f1e1bcec32385aa815b5b9d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2cb8c7008cd50c99f1e1bcec32385aa815b5b9d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,244 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c2/c2ce8fbb5ce0bf6a28a6dfab6f8aeaecba66e770.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c2/c2ce8fbb5ce0bf6a28a6dfab6f8aeaecba66e770.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + def test_default + 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.where(:is_default => true).first + assert_equal 1, DocumentCategory.default.id + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c2/c2d9673b89b0bb130fcd865ab508d36bb734ab0f.svn-base --- a/.svn/pristine/c2/c2d9673b89b0bb130fcd865ab508d36bb734ab0f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - - def results_scope(options={}) - order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) - - TimeEntry.visible. - where(statement). - order(order_option). - joins(joins_for_order_statement(order_option.join(','))) - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c33253e7d9fe3dcf6d331a6400b5f9b5e73ef2a5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c33253e7d9fe3dcf6d331a6400b5f9b5e73ef2a5.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,184 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c336c255f0c991cc835201626f5e37be82ee02c6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c336c255f0c991cc835201626f5e37be82ee02c6.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,536 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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] ||= {} + + if @@handler_options[:allow_override].is_a?(String) + @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) + end + @@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_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) + end + + # Extracts MailHandler options from environment variables + # Use when receiving emails with rake tasks + def self.extract_options_from_env(env) + options = {:issue => {}} + %w(project status tracker category priority).each do |option| + options[:issue][option.to_sym] = env[option] if env[option] + end + %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option| + options[option.to_sym] = env[option] if env[option] + end + options + end + + def logger + Rails.logger + end + + cattr_accessor :ignored_emails_headers + @@ignored_emails_headers = { + 'X-Auto-Response-Suppress' => 'oof', + 'Auto-Submitted' => /^auto-/ + } + + # 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 + if logger + logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" + end + return false + end + # Ignore auto generated emails + self.class.ignored_emails_headers.each do |key, ignored_value| + value = email.header[key] + if value + value = value.to_s.downcase + if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value + if logger + logger.info "MailHandler: ignoring email with #{key}:#{value} header" + end + return false + end + end + end + @user = User.find_by_mail(sender_email) if sender_email.present? + if @user && !@user.active? + if logger + logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" + end + 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 + if @user + if logger + logger.info "MailHandler: [#{@user.login}] account created" + end + 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 "MailHandler: could not create account for [#{sender_email}]" + end + return false + end + else + # Default behaviour, emails from unknown users are ignored + if logger + logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" + end + 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 = cleaned_up_subject + 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 + issue + end + + # Adds a note to an existing issue + def receive_issue_reply(issue_id, from_journal=nil) + issue = Issue.find_by_id(issue_id) + return unless issue + # check permission + unless @@handler_options[:no_permission_check] + unless user.allowed_to?(:add_issue_notes, issue.project) || + user.allowed_to?(:edit_issues, issue.project) + raise UnauthorizedAction + end + end + + # ignore CLI-supplied defaults for new issues + @@handler_options[:issue].clear + + journal = issue.init_journal(user) + if from_journal && from_journal.private_notes? + # If the received email was a reply to a private note, make the added note private + issue.private_notes = true + end + 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! + if logger + logger.info "MailHandler: issue ##{issue.id} updated by #{user}" + end + 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, journal) + 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 => cleaned_up_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 + if logger + logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" + end + end + end + end + + def add_attachments(obj) + if email.attachments && email.attachments.any? + email.attachments.each do |attachment| + next unless accept_attachment?(attachment) + obj.attachments << Attachment.create(:container => obj, + :file => attachment.decoded, + :filename => attachment.filename, + :author => user, + :content_type => attachment.mime_type) + end + end + end + + # Returns false if the +attachment+ of the incoming email should be ignored + def accept_attachment?(attachment) + @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?) + @excluded.each do |pattern| + regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i + if attachment.filename.to_s =~ regexp + logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}" + return false + end + end + true + 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.where('LOWER(mail) IN (?)', addresses).all + 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) + if user && user.language.present? + keys << l("field_#{attr}", :default => '', :locale => user.language) + end + if Setting.default_language.present? + keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) + end + end + keys.reject! {|k| k.blank?} + keys.collect! {|k| Regexp.escape(k)} + format ||= '.+' + keyword = nil + regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i + if m = text.match(regexp) + keyword = m[2].strip + text.gsub!(regexp, '') + end + keyword + 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)) + if target.nil? + # Invalid project keyword, use the project specified as the default one + default_project = @@handler_options[:issue][:project] + if default_project.present? + target = Project.find_by_identifier(default_project) + end + end + 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.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 keyword = get_keyword(v.custom_field.name, :override => true) + h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized) + 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 = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present? + text_parts + elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present? + html_parts + else + [email] + end + + parts.reject! do |part| + part.header[:content_disposition].try(:disposition_type) == 'attachment' + end + + @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n") + + # strip html tags and remove doctype directive + if parts.any? {|p| p.mime_type == 'text/html'} + @plain_text_body = strip_tags(@plain_text_body.strip) + @plain_text_body.sub! %r{^$/) + addr, name = m[2], m[1] + 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 + logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger + nil + end + else + logger.error "MailHandler: failed to create User: no FROM address found" if logger + nil + 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)} + unless delimiters.empty? + regex = Regexp.new("^[> ]*(#{ 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.name.downcase == keyword} + end + assignee + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3434dc1252ecfbed9052875659c7477dd01f91d.svn-base --- a/.svn/pristine/c3/c3434dc1252ecfbed9052875659c7477dd01f91d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - - def test_set_localization - Setting.default_language = 'en' - - # a french user - get 'projects', { }, 'HTTP_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', { }, 'HTTP_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', { }, 'HTTP_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 - - def test_missing_template_should_respond_with_404 - get '/login.png' - assert_response 404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3445c0cb311dd51294d15b24c99859759c6e162.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c3445c0cb311dd51294d15b24c99859759c6e162.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,175 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 AccountControllerOpenidTest < ActionController::TestCase + tests AccountController + fixtures :users, :roles + + def setup + User.current = nil + Setting.openid = '1' + end + + def teardown + Setting.openid = '0' + end + + if Object.const_defined?(:OpenID) + + def test_login_with_openid_for_existing_user + Setting.self_registration = '3' + 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' + 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' + 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' + 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' + post :login, :openid_url => 'http://openid.example.com/good_user' + assert_redirected_to home_url + user = User.find_by_login('cool_user') + assert_nil user + end + + def test_login_with_openid_with_new_user_created_with_email_activation_should_have_a_token + Setting.self_registration = '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' + 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' + 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_login_with_openid_with_new_user_with_missing_information_should_register + Setting.self_registration = '3' + + post :login, :openid_url => 'http://openid.example.com/good_blank_user' + assert_response :success + assert_template 'register' + assert assigns(:user) + assert_equal 'http://openid.example.com/good_blank_user', assigns(:user)[:identity_url] + + assert_select 'input[name=?]', 'user[login]' + assert_select 'input[name=?]', 'user[password]' + assert_select 'input[name=?]', 'user[password_confirmation]' + assert_select 'input[name=?][value=?]', 'user[identity_url]', 'http://openid.example.com/good_blank_user' + end + + def test_post_login_should_not_verify_token_when_using_open_id + ActionController::Base.allow_forgery_protection = true + AccountController.any_instance.stubs(:using_open_id?).returns(true) + AccountController.any_instance.stubs(:authenticate_with_open_id).returns(true) + post :login + assert_response 200 + ensure + ActionController::Base.allow_forgery_protection = false + end + + def test_register_after_login_failure_should_not_require_user_to_enter_a_password + Setting.self_registration = '3' + + assert_difference 'User.count' do + post :register, :user => { + :login => 'good_blank_user', + :password => '', + :password_confirmation => '', + :firstname => 'Cool', + :lastname => 'User', + :mail => 'user@somedomain.com', + :identity_url => 'http://openid.example.com/good_blank_user' + } + assert_response 302 + end + + user = User.first(:order => 'id DESC') + assert_equal 'http://openid.example.com/good_blank_user', user.identity_url + assert user.hashed_password.blank?, "Hashed password was #{user.hashed_password}" + end + + def test_setting_openid_should_return_true_when_set_to_true + assert_equal true, Setting.openid? + end + + else + puts "Skipping openid tests." + + def test_dummy + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c36c3ff1ad3e3f0290f0f72dd0de479a7596b93a.svn-base --- a/.svn/pristine/c3/c36c3ff1ad3e3f0290f0f72dd0de479a7596b93a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1086 +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_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: - 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: "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}) ได้ลงทะเบียน. บัญชีของเขากำลังรออนุมัติ:" - - - 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_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_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_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_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}" - 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: 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: จำนวนรวม - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3a64cc995ecf5031ca7af6e71034d712ca5328d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c3/c3a64cc995ecf5031ca7af6e71034d712ca5328d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,364 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + def test_offset_range_japanese_3 + # UTF-8 The 1st byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe5\xa8\x98" + 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-3.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_4 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98" + 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-4.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_5 + # UTF-8 The 2nd byte differs. + ja1 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xa8\x98ok" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x98ok" + 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-5.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3b028d6babb75599cb5d815af88fd567204c725.svn-base --- a/.svn/pristine/c3/c3b028d6babb75599cb5d815af88fd567204c725.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -

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

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

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

    -

    -<%= select_tag 'reassign_to_id', (content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

    -
    - -<%= submit_tag l(:button_apply) %> -<%= link_to l(:button_cancel), enumerations_path %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3cde01a42095e130d429cd695c3b938d274bb0b.svn-base --- a/.svn/pristine/c3/c3cde01a42095e130d429cd695c3b938d274bb0b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,438 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - include Redmine::SafeAttributes - - # Maximum length for repository identifiers - IDENTIFIER_MAX_LENGTH = 255 - - belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" - has_many :filechanges, :class_name => 'Change', :through => :changesets - - serialize :extra_info - - before_save :check_default - - # 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 - validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true - validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? } - 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 => /\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 - - safe_attributes 'identifier', - 'login', - 'password', - 'path_encoding', - 'log_encoding', - 'is_default' - - safe_attributes 'url', - :if => lambda {|repository, user| repository.new_record?} - - 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, *args) - attr_name = attribute_key_name.to_s - if attr_name == "log_encoding" - attr_name = "commit_logs_encoding" - end - super(attr_name, *args) - 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 - unless @scm - @scm = self.scm_adapter.new(url, root_url, - login, password, path_encoding) - if root_url.blank? && @scm.root_url.present? - update_attribute(:root_url, @scm.root_url) - end - end - @scm - end - - def scm_name - self.class.scm_name - end - - def name - if identifier.present? - identifier - elsif is_default? - l(:field_repository_is_default) - else - scm_name - end - end - - def identifier=(identifier) - super unless identifier_frozen? - end - - def identifier_frozen? - errors[:identifier].blank? && !(new_record? || identifier.blank?) - end - - def identifier_param - if is_default? - nil - elsif identifier.present? - identifier - else - id.to_s - end - end - - def <=>(repository) - if is_default? - -1 - elsif repository.is_default? - 1 - else - identifier.to_s <=> repository.identifier.to_s - end - end - - def self.find_by_identifier_param(param) - if param.to_s =~ /^\d+$/ - find_by_id(param) - else - find_by_identifier(param) - end - 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) - entries = scm.entries(path, identifier) - load_entries_changesets(entries) - entries - 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? - s = name.to_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.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 - filechanges.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.where(:committer => committer).includes(:user).first - 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).all.each do |project| - project.repositories.each do |repository| - begin - 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 - 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 - - def set_as_default? - new_record? && project && !Repository.first(:conditions => {:project_id => project.id}) - end - - protected - - def check_default - if !is_default? && set_as_default? - self.is_default = true - end - if is_default? && is_default_changed? - Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id]) - end - end - - def load_entries_changesets(entries) - if entries - entries.each do |entry| - if entry.lastrev && entry.lastrev.identifier - entry.changeset = find_changeset_by_name(entry.lastrev.identifier) - end - end - end - end - - private - - # Deletes repository data - def clear_changesets - cs = Changeset.table_name - ch = Change.table_name - ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}" - cp = "#{table_name_prefix}changeset_parents#{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 #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") - clear_extra_info_of_changesets - end - - def clear_extra_info_of_changesets - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c3/c3ef157204ed32856d2cce37cd8fd93fa5678cb4.svn-base --- a/.svn/pristine/c3/c3ef157204ed32856d2cce37cd8fd93fa5678cb4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,933 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 WikiControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, - :enabled_modules, :wikis, :wiki_pages, :wiki_contents, - :wiki_content_versions, :attachments - - def setup - User.current = nil - end - - def test_show_start_page - get :show, :project_id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_tag :tag => 'h1', :content => /CookBook documentation/ - - # child_pages macro - assert_tag :ul, :attributes => { :class => 'pages-hierarchy' }, - :child => { :tag => 'li', - :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' }, - :content => 'Page with an inline image' } } - end - - def test_export_link - Role.anonymous.add_permission! :export_wiki_pages - get :show, :project_id => 'ecookbook' - assert_response :success - assert_tag 'a', :attributes => {:href => '/projects/ecookbook/wiki/CookBook_documentation.txt'} - end - - def test_show_page_with_name - get :show, :project_id => 1, :id => 'Another_page' - assert_response :success - assert_template 'show' - 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/logo.gif', - :alt => 'This is a logo' } - end - - def test_show_old_version - get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '2' - assert_response :success - assert_template 'show' - - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/1', :text => /Previous/ - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/diff', :text => /diff/ - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/3', :text => /Next/ - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/ - end - - def test_show_old_version_with_attachments - page = WikiPage.find(4) - assert page.attachments.any? - content = page.content - content.text = "update" - content.save! - - get :show, :project_id => 'ecookbook', :id => page.title, :version => '1' - assert_kind_of WikiContent::Version, assigns(:content) - assert_response :success - assert_template 'show' - end - - def test_show_old_version_without_permission_should_be_denied - Role.anonymous.remove_permission! :view_wiki_edits - - get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '2' - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fprojects%2Fecookbook%2Fwiki%2FCookBook_documentation%2F2' - end - - def test_show_first_version - get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '1' - assert_response :success - assert_template 'show' - - assert_select 'a', :text => /Previous/, :count => 0 - assert_select 'a', :text => /diff/, :count => 0 - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => /Next/ - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/ - end - - def test_show_redirected_page - WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page') - - get :show, :project_id => 'ecookbook', :id => 'Old_title' - assert_redirected_to '/projects/ecookbook/wiki/Another_page' - end - - def test_show_with_sidebar - page = Project.find(1).wiki.pages.new(:title => 'Sidebar') - page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar') - page.save! - - get :show, :project_id => 1, :id => 'Another_page' - assert_response :success - assert_tag :tag => 'div', :attributes => {:id => 'sidebar'}, - :content => /Side bar content for test_show_with_sidebar/ - end - - def test_show_should_display_section_edit_links - @request.session[:user_id] = 2 - get :show, :project_id => 1, :id => 'Page with sections' - assert_no_tag 'a', :attributes => { - :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=1' - } - assert_tag 'a', :attributes => { - :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2' - } - assert_tag 'a', :attributes => { - :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=3' - } - end - - def test_show_current_version_should_display_section_edit_links - @request.session[:user_id] = 2 - get :show, :project_id => 1, :id => 'Page with sections', :version => 3 - - assert_tag 'a', :attributes => { - :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2' - } - end - - def test_show_old_version_should_not_display_section_edit_links - @request.session[:user_id] = 2 - get :show, :project_id => 1, :id => 'Page with sections', :version => 2 - - assert_no_tag 'a', :attributes => { - :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2' - } - end - - def test_show_unexistent_page_without_edit_right - get :show, :project_id => 1, :id => 'Unexistent page' - assert_response 404 - end - - def test_show_unexistent_page_with_edit_right - @request.session[:user_id] = 2 - get :show, :project_id => 1, :id => 'Unexistent page' - assert_response :success - assert_template 'edit' - end - - def test_show_unexistent_page_with_parent_should_preselect_parent - @request.session[:user_id] = 2 - get :show, :project_id => 1, :id => 'Unexistent page', :parent => 'Another_page' - assert_response :success - assert_template 'edit' - assert_tag 'select', :attributes => {:name => 'wiki_page[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}} - end - - def test_show_should_not_show_history_without_permission - Role.anonymous.remove_permission! :view_wiki_edits - get :show, :project_id => 1, :id => 'Page with sections', :version => 2 - - assert_response 302 - end - - def test_create_page - @request.session[:user_id] = 2 - assert_difference 'WikiPage.count' do - assert_difference 'WikiContent.count' do - put :update, :project_id => 1, - :id => 'New page', - :content => {:comments => 'Created the page', - :text => "h1. New page\n\nThis is a new page", - :version => 0} - end - end - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page' - page = Project.find(1).wiki.find_page('New page') - assert !page.new_record? - assert_not_nil page.content - assert_nil page.parent - assert_equal 'Created the page', page.content.comments - end - - def test_create_page_with_attachments - @request.session[:user_id] = 2 - assert_difference 'WikiPage.count' do - assert_difference 'Attachment.count' do - put :update, :project_id => 1, - :id => 'New page', - :content => {:comments => 'Created the page', - :text => "h1. New page\n\nThis is a new page", - :version => 0}, - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - end - end - page = Project.find(1).wiki.find_page('New page') - assert_equal 1, page.attachments.count - assert_equal 'testfile.txt', page.attachments.first.filename - end - - def test_create_page_with_parent - @request.session[:user_id] = 2 - assert_difference 'WikiPage.count' do - put :update, :project_id => 1, :id => 'New page', - :content => {:text => "h1. New page\n\nThis is a new page", :version => 0}, - :wiki_page => {:parent_id => 2} - end - page = Project.find(1).wiki.find_page('New page') - assert_equal WikiPage.find(2), page.parent - end - - def test_edit_page - @request.session[:user_id] = 2 - get :edit, :project_id => 'ecookbook', :id => 'Another_page' - - assert_response :success - assert_template 'edit' - - assert_tag 'textarea', - :attributes => { :name => 'content[text]' }, - :content => "\n"+WikiPage.find_by_title('Another_page').content.text - end - - def test_edit_section - @request.session[:user_id] = 2 - get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2 - - assert_response :success - assert_template 'edit' - - page = WikiPage.find_by_title('Page_with_sections') - section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2) - - assert_tag 'textarea', - :attributes => { :name => 'content[text]' }, - :content => "\n"+section - assert_tag 'input', - :attributes => { :name => 'section', :type => 'hidden', :value => '2' } - assert_tag 'input', - :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash } - end - - def test_edit_invalid_section_should_respond_with_404 - @request.session[:user_id] = 2 - get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10 - - assert_response 404 - end - - def test_update_page - @request.session[:user_id] = 2 - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => "my comments", - :text => "edited", - :version => 1 - } - end - end - end - assert_redirected_to '/projects/ecookbook/wiki/Another_page' - - page = Wiki.find(1).pages.find_by_title('Another_page') - assert_equal "edited", page.content.text - assert_equal 2, page.content.version - assert_equal "my comments", page.content.comments - end - - def test_update_page_with_parent - @request.session[:user_id] = 2 - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => "my comments", - :text => "edited", - :version => 1 - }, - :wiki_page => {:parent_id => '1'} - end - end - end - assert_redirected_to '/projects/ecookbook/wiki/Another_page' - - page = Wiki.find(1).pages.find_by_title('Another_page') - assert_equal "edited", page.content.text - assert_equal 2, page.content.version - assert_equal "my comments", page.content.comments - assert_equal WikiPage.find(1), page.parent - end - - def test_update_page_with_failure - @request.session[:user_id] = 2 - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => 'a' * 300, # failure here, comment is too long - :text => 'edited', - :version => 1 - } - end - end - end - assert_response :success - assert_template 'edit' - - assert_error_tag :descendant => {:content => /Comment is too long/} - assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => "\nedited" - assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'} - end - - def test_update_page_with_parent_change_only_should_not_create_content_version - @request.session[:user_id] = 2 - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => '', - :text => Wiki.find(1).find_page('Another_page').content.text, - :version => 1 - }, - :wiki_page => {:parent_id => '1'} - end - end - end - page = Wiki.find(1).pages.find_by_title('Another_page') - assert_equal 1, page.content.version - assert_equal WikiPage.find(1), page.parent - end - - def test_update_page_with_attachments_only_should_not_create_content_version - @request.session[:user_id] = 2 - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiContent::Version.count' do - assert_difference 'Attachment.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => '', - :text => Wiki.find(1).find_page('Another_page').content.text, - :version => 1 - }, - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} - end - end - end - end - page = Wiki.find(1).pages.find_by_title('Another_page') - assert_equal 1, page.content.version - end - - def test_update_stale_page_should_not_raise_an_error - @request.session[:user_id] = 2 - c = Wiki.find(1).find_page('Another_page').content - c.text = 'Previous text' - c.save! - assert_equal 2, c.version - - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, - :id => 'Another_page', - :content => { - :comments => 'My comments', - :text => 'Text should not be lost', - :version => 1 - } - end - end - end - assert_response :success - assert_template 'edit' - assert_tag :div, - :attributes => { :class => /error/ }, - :content => /Data has been updated by another user/ - assert_tag 'textarea', - :attributes => { :name => 'content[text]' }, - :content => /Text should not be lost/ - assert_tag 'input', - :attributes => { :name => 'content[comments]', :value => 'My comments' } - - c.reload - assert_equal 'Previous text', c.text - assert_equal 2, c.version - end - - def test_update_section - @request.session[:user_id] = 2 - page = WikiPage.find_by_title('Page_with_sections') - section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2) - text = page.content.text - - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, :id => 'Page_with_sections', - :content => { - :text => "New section content", - :version => 3 - }, - :section => 2, - :section_hash => hash - end - end - end - assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections' - assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text - end - - def test_update_section_should_allow_stale_page_update - @request.session[:user_id] = 2 - page = WikiPage.find_by_title('Page_with_sections') - section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2) - text = page.content.text - - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, :id => 'Page_with_sections', - :content => { - :text => "New section content", - :version => 2 # Current version is 3 - }, - :section => 2, - :section_hash => hash - end - end - end - assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections' - page.reload - assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text - assert_equal 4, page.content.version - end - - def test_update_section_should_not_allow_stale_section_update - @request.session[:user_id] = 2 - - assert_no_difference 'WikiPage.count' do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiContent::Version.count' do - put :update, :project_id => 1, :id => 'Page_with_sections', - :content => { - :comments => 'My comments', - :text => "Text should not be lost", - :version => 3 - }, - :section => 2, - :section_hash => Digest::MD5.hexdigest("wrong hash") - end - end - end - assert_response :success - assert_template 'edit' - assert_tag :div, - :attributes => { :class => /error/ }, - :content => /Data has been updated by another user/ - assert_tag 'textarea', - :attributes => { :name => 'content[text]' }, - :content => /Text should not be lost/ - assert_tag 'input', - :attributes => { :name => 'content[comments]', :value => 'My comments' } - end - - def test_preview - @request.session[:user_id] = 2 - xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation', - :content => { :comments => '', - :text => 'this is a *previewed text*', - :version => 3 } - assert_response :success - assert_template 'common/_preview' - assert_tag :tag => 'strong', :content => /previewed text/ - end - - def test_preview_new_page - @request.session[:user_id] = 2 - xhr :post, :preview, :project_id => 1, :id => 'New page', - :content => { :text => 'h1. New page', - :comments => '', - :version => 0 } - assert_response :success - assert_template 'common/_preview' - assert_tag :tag => 'h1', :content => /New page/ - end - - def test_history - @request.session[:user_id] = 2 - get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation' - assert_response :success - assert_template 'history' - assert_not_nil assigns(:versions) - assert_equal 3, assigns(:versions).size - - assert_select "input[type=submit][name=commit]" - assert_select 'td' do - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2' - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate', :text => 'Annotate' - assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => 'Delete' - end - end - - def test_history_with_one_version - @request.session[:user_id] = 2 - get :history, :project_id => 'ecookbook', :id => 'Another_page' - assert_response :success - assert_template 'history' - assert_not_nil assigns(:versions) - assert_equal 1, assigns(:versions).size - assert_select "input[type=submit][name=commit]", false - assert_select 'td' do - assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => '1' - assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1/annotate', :text => 'Annotate' - assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => 'Delete', :count => 0 - end - end - - def test_diff - content = WikiPage.find(1).content - assert_difference 'WikiContent::Version.count', 2 do - content.text = "Line removed\nThis is a sample text for testing diffs" - content.save! - content.text = "This is a sample text for testing diffs\nLine added" - content.save! - end - - get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => content.version, :version_from => (content.version - 1) - assert_response :success - assert_template 'diff' - assert_select 'span.diff_out', :text => 'Line removed' - assert_select 'span.diff_in', :text => 'Line added' - end - - def test_diff_with_invalid_version_should_respond_with_404 - get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => '99' - assert_response 404 - end - - def test_diff_with_invalid_version_from_should_respond_with_404 - get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => '99', :version_from => '98' - assert_response 404 - end - - def test_annotate - get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2 - assert_response :success - assert_template 'annotate' - - # Line 1 - assert_tag :tag => 'tr', :child => { - :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => { - :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => { - :tag => 'td', :content => /h1\. CookBook documentation/ - } - } - } - - # 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', :content => /Some updated \[\[documentation\]\] here/ - } - } - } - end - - def test_annotate_with_invalid_version_should_respond_with_404 - get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => '99' - assert_response 404 - end - - def test_get_rename - @request.session[:user_id] = 2 - get :rename, :project_id => 1, :id => 'Another_page' - assert_response :success - assert_template 'rename' - assert_tag 'option', - :attributes => {:value => ''}, - :content => '', - :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}} - assert_no_tag 'option', - :attributes => {:selected => 'selected'}, - :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}} - end - - def test_get_rename_child_page - @request.session[:user_id] = 2 - get :rename, :project_id => 1, :id => 'Child_1' - assert_response :success - assert_template 'rename' - assert_tag 'option', - :attributes => {:value => ''}, - :content => '', - :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}} - assert_tag 'option', - :attributes => {:value => '2', :selected => 'selected'}, - :content => /Another page/, - :parent => { - :tag => 'select', - :attributes => {:name => 'wiki_page[parent_id]'} - } - end - - def test_rename_with_redirect - @request.session[:user_id] = 2 - post :rename, :project_id => 1, :id => 'Another_page', - :wiki_page => { :title => 'Another renamed page', - :redirect_existing_links => 1 } - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page' - wiki = Project.find(1).wiki - # Check redirects - assert_not_nil wiki.find_page('Another page') - assert_nil wiki.find_page('Another page', :with_redirect => false) - end - - def test_rename_without_redirect - @request.session[:user_id] = 2 - post :rename, :project_id => 1, :id => 'Another_page', - :wiki_page => { :title => 'Another renamed page', - :redirect_existing_links => "0" } - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page' - wiki = Project.find(1).wiki - # Check that there's no redirects - assert_nil wiki.find_page('Another page') - end - - def test_rename_with_parent_assignment - @request.session[:user_id] = 2 - post :rename, :project_id => 1, :id => 'Another_page', - :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' } - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page' - assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent - end - - def test_rename_with_parent_unassignment - @request.session[:user_id] = 2 - post :rename, :project_id => 1, :id => 'Child_1', - :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' } - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1' - assert_nil WikiPage.find_by_title('Child_1').parent - end - - def test_destroy_a_page_without_children_should_not_ask_confirmation - @request.session[:user_id] = 2 - delete :destroy, :project_id => 1, :id => 'Child_2' - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - end - - def test_destroy_parent_should_ask_confirmation - @request.session[:user_id] = 2 - assert_no_difference('WikiPage.count') do - delete :destroy, :project_id => 1, :id => 'Another_page' - end - assert_response :success - assert_template 'destroy' - assert_select 'form' do - assert_select 'input[name=todo][value=nullify]' - assert_select 'input[name=todo][value=destroy]' - assert_select 'input[name=todo][value=reassign]' - end - end - - def test_destroy_parent_with_nullify_should_delete_parent_only - @request.session[:user_id] = 2 - assert_difference('WikiPage.count', -1) do - delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify' - end - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - assert_nil WikiPage.find_by_id(2) - end - - def test_destroy_parent_with_cascade_should_delete_descendants - @request.session[:user_id] = 2 - assert_difference('WikiPage.count', -4) do - delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy' - end - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - assert_nil WikiPage.find_by_id(2) - assert_nil WikiPage.find_by_id(5) - end - - def test_destroy_parent_with_reassign - @request.session[:user_id] = 2 - assert_difference('WikiPage.count', -1) do - delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1 - end - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - assert_nil WikiPage.find_by_id(2) - assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent - end - - def test_destroy_version - @request.session[:user_id] = 2 - assert_difference 'WikiContent::Version.count', -1 do - assert_no_difference 'WikiContent.count' do - assert_no_difference 'WikiPage.count' do - delete :destroy_version, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => 2 - assert_redirected_to '/projects/ecookbook/wiki/CookBook_documentation/history' - end - end - end - end - - def test_index - get :index, :project_id => 'ecookbook' - assert_response :success - assert_template 'index' - pages = assigns(:pages) - assert_not_nil pages - assert_equal Project.find(1).wiki.pages.size, pages.size - assert_equal pages.first.content.updated_on, pages.first.updated_on - - assert_tag :ul, :attributes => { :class => 'pages-hierarchy' }, - :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' }, - :content => 'CookBook documentation' }, - :child => { :tag => 'ul', - :child => { :tag => 'li', - :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' }, - :content => 'Page with an inline image' } } } }, - :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' }, - :content => 'Another page' } } - end - - def test_index_should_include_atom_link - get :index, :project_id => 'ecookbook' - assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'} - end - - def test_export_to_html - @request.session[:user_id] = 2 - get :export, :project_id => 'ecookbook' - - assert_response :success - assert_not_nil assigns(:pages) - assert assigns(:pages).any? - assert_equal "text/html", @response.content_type - - assert_select "a[name=?]", "CookBook_documentation" - assert_select "a[name=?]", "Another_page" - assert_select "a[name=?]", "Page_with_an_inline_image" - end - - def test_export_to_pdf - @request.session[:user_id] = 2 - get :export, :project_id => 'ecookbook', :format => 'pdf' - - assert_response :success - assert_not_nil assigns(:pages) - assert assigns(:pages).any? - assert_equal 'application/pdf', @response.content_type - assert_equal 'attachment; filename="ecookbook.pdf"', @response.headers['Content-Disposition'] - assert @response.body.starts_with?('%PDF') - end - - def test_export_without_permission_should_be_denied - @request.session[:user_id] = 2 - Role.find_by_name('Manager').remove_permission! :export_wiki_pages - get :export, :project_id => 'ecookbook' - - assert_response 403 - end - - def test_date_index - get :date_index, :project_id => 'ecookbook' - - assert_response :success - assert_template 'date_index' - assert_not_nil assigns(:pages) - assert_not_nil assigns(:pages_by_date) - - assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'} - end - - def test_not_found - get :show, :project_id => 999 - assert_response 404 - end - - def test_protect_page - page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page') - assert !page.protected? - @request.session[:user_id] = 2 - post :protect, :project_id => 1, :id => page.title, :protected => '1' - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page' - assert page.reload.protected? - end - - def test_unprotect_page - page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation') - assert page.protected? - @request.session[:user_id] = 2 - post :protect, :project_id => 1, :id => page.title, :protected => '0' - assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation' - assert !page.reload.protected? - end - - def test_show_page_with_edit_link - @request.session[:user_id] = 2 - get :show, :project_id => 1 - assert_response :success - assert_template 'show' - assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' } - end - - def test_show_page_without_edit_link - @request.session[:user_id] = 4 - get :show, :project_id => 1 - assert_response :success - assert_template 'show' - assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' } - end - - def test_show_pdf - @request.session[:user_id] = 2 - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_not_nil assigns(:page) - assert_equal 'application/pdf', @response.content_type - assert_equal 'attachment; filename="CookBook_documentation.pdf"', - @response.headers['Content-Disposition'] - end - - def test_show_html - @request.session[:user_id] = 2 - get :show, :project_id => 1, :format => 'html' - assert_response :success - assert_not_nil assigns(:page) - assert_equal 'text/html', @response.content_type - assert_equal 'attachment; filename="CookBook_documentation.html"', - @response.headers['Content-Disposition'] - assert_tag 'h1', :content => 'CookBook documentation' - end - - def test_show_versioned_html - @request.session[:user_id] = 2 - get :show, :project_id => 1, :format => 'html', :version => 2 - assert_response :success - assert_not_nil assigns(:content) - assert_equal 2, assigns(:content).version - assert_equal 'text/html', @response.content_type - assert_equal 'attachment; filename="CookBook_documentation.html"', - @response.headers['Content-Disposition'] - assert_tag 'h1', :content => 'CookBook documentation' - end - - def test_show_txt - @request.session[:user_id] = 2 - get :show, :project_id => 1, :format => 'txt' - assert_response :success - assert_not_nil assigns(:page) - assert_equal 'text/plain', @response.content_type - assert_equal 'attachment; filename="CookBook_documentation.txt"', - @response.headers['Content-Disposition'] - assert_include 'h1. CookBook documentation', @response.body - end - - def test_show_versioned_txt - @request.session[:user_id] = 2 - get :show, :project_id => 1, :format => 'txt', :version => 2 - assert_response :success - assert_not_nil assigns(:content) - assert_equal 2, assigns(:content).version - assert_equal 'text/plain', @response.content_type - assert_equal 'attachment; filename="CookBook_documentation.txt"', - @response.headers['Content-Disposition'] - assert_include 'h1. CookBook documentation', @response.body - end - - def test_edit_unprotected_page - # Non members can edit unprotected wiki pages - @request.session[:user_id] = 4 - get :edit, :project_id => 1, :id => 'Another_page' - assert_response :success - assert_template 'edit' - end - - def test_edit_protected_page_by_nonmember - # Non members can't edit protected wiki pages - @request.session[:user_id] = 4 - get :edit, :project_id => 1, :id => 'CookBook_documentation' - assert_response 403 - end - - def test_edit_protected_page_by_member - @request.session[:user_id] = 2 - get :edit, :project_id => 1, :id => 'CookBook_documentation' - assert_response :success - assert_template 'edit' - end - - def test_history_of_non_existing_page_should_return_404 - get :history, :project_id => 1, :id => 'Unknown_page' - assert_response 404 - end - - def test_add_attachment - @request.session[:user_id] = 2 - assert_difference 'Attachment.count' do - post :add_attachment, :project_id => 1, :id => 'CookBook_documentation', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} - end - attachment = Attachment.first(:order => 'id DESC') - assert_equal Wiki.find(1).find_page('CookBook_documentation'), attachment.container - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c4/c427c4a0d41e44c20850962dc38a70772b208946.svn-base --- a/.svn/pristine/c4/c427c4a0d41e44c20850962dc38a70772b208946.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1078 +0,0 @@ -uk: - 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_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: - 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: "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: 'Ukrainian (Українська)' - 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_scm_error: Запису та/або виправлення немає в репозиторії. - 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: "Ваш обліковий запис створено і він чекає на підтвердження адміністратором." - - 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: Регулярний вираз - 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: Target 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: Атрибут 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: Вживається у пошуку - - 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_host_name: Им'я машини - setting_text_formatting: Форматування тексту - setting_wiki_compression: Стиснення історії Wiki - setting_feeds_limit: Обмеження змісту подачі - 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: Протокол - - 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_document: Документ - label_document_new: Новий документ - label_document_plural: Документи - 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_new: Новий - label_logged_as: Увійшов як - label_environment: Оточення - label_authentication: Аутентифікація - label_auth_source: Режим аутентифікації - label_auth_source_new: Новий режим аутентифікації - label_auth_source_plural: Режими аутентифікації - 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_report: Звіт - label_report_plural: Звіти - label_news: Новини - label_news_new: Додати новину - label_news_plural: Новини - label_news_latest: Останні новини - label_news_view_all: Подивитися всі новини - 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: Діаграма Ганта - label_internal: Внутрішній - label_last_changes: "останні %{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_this_week: цього тижня - label_less_than_ago: менш ніж днів(я) назад - label_more_than_ago: більш ніж днів(я) назад - label_ago: днів(я) назад - label_contains: містить - label_not_contains: не містить - label_day_plural: днів(я) - label_repository: Репозиторій - label_browse: Проглянути - label_modification: "%{count} зміна" - label_modification_plural: "%{count} змін" - label_revision: Версія - label_revision_plural: Версій - label_added: додано - label_modified: змінене - label_deleted: видалено - label_latest_revision: Остання версія - label_latest_revision_plural: Останні версії - label_view_revisions: Проглянути версії - label_max_size: Максимальний розмір - label_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_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_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_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: "Оновлений %{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_my_time_report: Мій звіт витраченого часу - - 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: Анотувати - - status_active: Активний - status_registered: Зареєстрований - status_locked: Заблокований - - text_select_mail_notifications: Виберіть дії, на які відсилатиметься повідомлення на електронну пошту. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 означає відсутність заборон - text_project_destroy_confirmation: Ви наполягаєте на видаленні цього проекту і всієї інформації, що відноситься до нього? - text_workflow_edit: Виберіть роль і координатор для редагування послідовності дій - text_are_you_sure: Ви впевнені? - 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_issues_ref_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: Ви впевнені, що хочете видалити цю wiki і весь зміст? - text_issue_category_destroy_question: "Декілька питань (%{count}) призначено в цю категорію. Що ви хочете зробити?" - text_issue_category_destroy_assignments: Видалити призначення категорії - text_issue_category_reassign_to: Перепризначити задачі до даної категорії - text_user_mail_option: "Для невибраних проектів ви отримуватимете повідомлення тільки про те, що проглядаєте або в чому берете участь (наприклад, питання автором яких ви є або які вам призначені)." - - default_role_manager: Менеджер - default_role_developer: Розробник - default_role_reporter: Репортер - # default_tracker_bug: Bug - # звітів default_tracker_bug: Помилка - 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_status_changed_by_changeset: "Applied in changeset %{value}." - label_display_per_page: "Per page: %{value}" - label_issue_added: Issue added - label_issue_updated: Issue updated - setting_per_page_options: Objects per page options - notice_default_data_loaded: Default configuration successfully loaded. - error_scm_not_found: "Entry and/or revision doesn't exist in the repository." - label_associated_revisions: Associated revisions - label_document_added: Document added - label_message_posted: Message added - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' - error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - setting_user_format: Users display format - label_age: Age - label_file_added: File added - label_more: More - field_default_value: Default value - label_scm: SCM - label_general: General - button_update: Update - text_select_project_modules: 'Select modules to enable for this project:' - label_change_properties: Change properties - text_load_default_configuration: Load the default configuration - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - label_news_added: News added - label_repository_plural: Repositories - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - project_module_boards: Boards - project_module_issue_tracking: Issue tracking - project_module_wiki: Wiki - project_module_files: Files - project_module_documents: Documents - project_module_repository: Repository - project_module_news: News - project_module_time_tracking: Time tracking - text_file_repository_writable: File repository writable - text_default_administrator_account_changed: Default administrator account changed - text_rmagick_available: RMagick available (optional) - button_configure: Configure - label_plugins: Plugins - label_ldap_authentication: LDAP authentication - label_downloads_abbr: D/L - label_this_month: this month - label_last_n_days: "last %{count} days" - label_all_time: all time - label_this_year: this year - label_date_range: Date range - label_last_week: last week - label_yesterday: yesterday - label_last_month: last month - label_add_another_file: Add another file - label_optional_description: Optional description - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do ?" - error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' - text_assign_time_entries_to_project: Assign reported hours to the project - text_destroy_time_entries: Delete reported hours - text_reassign_time_entries: 'Reassign reported hours to this issue:' - setting_activity_days_default: Days displayed on project activity - label_chronological_order: In chronological order - field_comments_sorting: Display comments - label_reverse_chronological_order: In reverse chronological order - label_preferences: Preferences - setting_display_subprojects_issues: Display subprojects issues on main projects by default - label_overall_activity: Overall activity - setting_default_projects_public: New projects are public by default - error_scm_annotate: "The entry does not exist or can not be annotated." - label_planning: Planning - text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." - 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}" - 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}) - 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: "Ваш обліковій запис повністю видалений" - setting_unsubscribe: "Дозволити користувачам видаляти свої облікові записи" - button_delete_my_account: "Видалити мій обліковий запис" - text_account_destroy_confirmation: "Ваш обліковий запис буде повністю видалений без можливості відновлення.\nВи певні, что бажаете продовжити?" - 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: 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c4/c4531d47cc56461cf6b79ce17579ddd86c41ae44.svn-base --- a/.svn/pristine/c4/c4531d47cc56461cf6b79ce17579ddd86c41ae44.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c4/c481d11c94e3c3761d0a696830bd24523a9fba1c.svn-base --- a/.svn/pristine/c4/c481d11c94e3c3761d0a696830bd24523a9fba1c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c4/c4c79c5f62cd0556288272ff3c097890e44b9076.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c4/c4c79c5f62cd0556288272ff3c097890e44b9076.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c52f7b6c9c463e8a51ddc62dfba2a59799b361b2.svn-base --- a/.svn/pristine/c5/c52f7b6c9c463e8a51ddc62dfba2a59799b361b2.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,294 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c585c9323b1f894b886cb662ebd3c804674a5129.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c585c9323b1f894b886cb662ebd3c804674a5129.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,54 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c5875d3c8e61841ef5ad08f475a3db29a1da98e1.svn-base --- a/.svn/pristine/c5/c5875d3c8e61841ef5ad08f475a3db29a1da98e1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, :enabled_modules - - 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/1/archive" - 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/1/unarchive" - assert_redirected_to "/admin/projects" - assert Project.find(1).active? - get "projects/1" - assert_response :success - end - - def test_modules_should_not_allow_get - assert_no_difference 'EnabledModule.count' do - get '/projects/1/modules', {:enabled_module_names => ['']}, credentials('jsmith') - assert_response 404 - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c5ac85b1b901fa66f9ca32fcaa2fc8702a7953a0.svn-base --- a/.svn/pristine/c5/c5ac85b1b901fa66f9ca32fcaa2fc8702a7953a0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class UsersController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :show - before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] - accept_api_auth :index, :show, :create, :update, :destroy - - helper :sort - include SortHelper - helper :custom_fields - include CustomFieldsHelper - - def index - sort_init 'login', 'asc' - sort_update %w(login firstname lastname mail admin created_on last_login_on) - - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = per_page_option - end - - @status = params[:status] || 1 - - scope = User.logged.status(@status) - scope = scope.like(params[:name]) if params[:name].present? - 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 - - respond_to do |format| - format.html { - @groups = Group.all.sort - render :layout => !request.xhr? - } - format.api - end - end - - def show - # show projects based on current user visibility - @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current)) - - events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) - @events_by_day = events.group_by(&:event_date) - - unless User.current.admin? - if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?) - render_404 - return - end - end - - respond_to do |format| - format.html { render :layout => 'base' } - format.api - end - end - - def new - @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @auth_sources = AuthSource.find(:all) - end - - def create - @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @user.safe_attributes = params[:user] - @user.admin = params[:user][:admin] || false - @user.login = params[:user][:login] - @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id - - if @user.save - @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') - @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - - Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information] - - 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} - ) - } - format.api { render :action => 'show', :status => :created, :location => user_url(@user) } - end - else - @auth_sources = AuthSource.find(:all) - # Clear password input - @user.password = @user.password_confirmation = nil - - respond_to do |format| - format.html { render :action => 'new' } - format.api { render_validation_errors(@user) } - end - end - end - - def edit - @auth_sources = AuthSource.find(:all) - @membership ||= Member.new - end - - def update - @user.admin = params[:user][:admin] if params[:user][:admin] - @user.login = params[:user][:login] if params[:user][:login] - if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) - @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] - end - @user.safe_attributes = params[:user] - # Was the account actived ? (do it before User#save clears the change) - was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) - # TODO: Similar to My#account - @user.pref.attributes = params[:pref] - @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') - - if @user.save - @user.pref.save - @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) - - if was_activated - Mailer.account_activated(@user).deliver - elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil? - Mailer.account_information(@user, params[:user][:password]).deliver - end - - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_to_referer_or edit_user_path(@user) - } - format.api { render_api_ok } - end - else - @auth_sources = AuthSource.find(:all) - @membership ||= Member.new - # Clear password input - @user.password = @user.password_confirmation = nil - - respond_to do |format| - format.html { render :action => :edit } - format.api { render_validation_errors(@user) } - end - end - end - - def destroy - @user.destroy - respond_to do |format| - format.html { redirect_back_or_default(users_url) } - format.api { render_api_ok } - end - end - - def edit_membership - @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.js - end - end - - def destroy_membership - @membership = Member.find(params[:membership_id]) - if @membership.deletable? - @membership.destroy - end - respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } - format.js - end - end - - private - - def find_user - if params[:id] == 'current' - require_login || return - @user = User.current - else - @user = User.find(params[:id]) - end - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c5babbe46fab862c827420531f4d70904c54b5e3.svn-base --- a/.svn/pristine/c5/c5babbe46fab862c827420531f4d70904c54b5e3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,373 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - - include Redmine::I18n - - def setup - @repository = Project.find(1).repository - end - - def test_blank_log_encoding_error_message - set_language_if_valid 'en' - repo = Repository::Bazaar.new( - :project => Project.find(3), - :url => "/test", - :log_encoding => '' - ) - assert !repo.save - assert_include "Commit messages encoding can't be blank", - repo.errors.full_messages - end - - def test_blank_log_encoding_error_message_fr - set_language_if_valid 'fr' - str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)" - str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) - repo = Repository::Bazaar.new( - :project => Project.find(3), - :url => "/test" - ) - assert !repo.save - assert_include str, repo.errors.full_messages - 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_first_repository_should_be_set_as_default - repository1 = Repository::Subversion.new( - :project => Project.find(3), - :identifier => 'svn1', - :url => 'file:///svn1' - ) - assert repository1.save - assert repository1.is_default? - - repository2 = Repository::Subversion.new( - :project => Project.find(3), - :identifier => 'svn2', - :url => 'file:///svn2' - ) - assert repository2.save - assert !repository2.is_default? - - assert_equal repository1, Project.find(3).repository - assert_equal [repository1, repository2], Project.find(3).repositories.sort - end - - def test_identifier_should_accept_letters_digits_dashes_and_underscores - r = Repository::Subversion.new( - :project_id => 3, - :identifier => 'svn-123_45', - :url => 'file:///svn' - ) - assert r.save - end - - def test_identifier_should_not_be_frozen_for_a_new_repository - assert_equal false, Repository.new.identifier_frozen? - end - - def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier - Repository.update_all(["identifier = ''"], "id = 10") - - assert_equal false, Repository.find(10).identifier_frozen? - end - - def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier - Repository.update_all(["identifier = 'abc123'"], "id = 10") - - assert_equal true, Repository.find(10).identifier_frozen? - end - - def test_identifier_should_not_accept_change_if_frozen - r = Repository.new(:identifier => 'foo') - r.stubs(:identifier_frozen?).returns(true) - - r.identifier = 'bar' - assert_equal 'foo', r.identifier - end - - def test_identifier_should_accept_change_if_not_frozen - r = Repository.new(:identifier => 'foo') - r.stubs(:identifier_frozen?).returns(false) - - r.identifier = 'bar' - assert_equal 'bar', r.identifier - end - - def test_destroy - repository = Repository.find(10) - changesets = repository.changesets.count - changes = repository.filechanges.count - - assert_difference 'Changeset.count', -changesets do - assert_difference 'Change.count', -changes do - Repository.find(10).destroy - end - end - end - - def test_destroy_should_delete_parents_associations - changeset = Changeset.find(102) - changeset.parents = Changeset.find_all_by_id([100, 101]) - - assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do - Repository.find(10).destroy - end - end - - def test_destroy_should_delete_issues_associations - changeset = Changeset.find(102) - changeset.issues = Issue.find_all_by_id([1, 2]) - - assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do - Repository.find(10).destroy - 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_include I18n.translate('activerecord.errors.messages.invalid'), - repository.errors[:type] - end - end - - def test_scan_changesets_for_issue_ids - Setting.default_language = 'en' - - # choosing a status to apply to fix issues - Setting.commit_fix_status_id = IssueStatus.find( - :first, - :conditions => ["is_closed = ?", true]).id - Setting.commit_fix_done_ratio = "90" - 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 - - with_settings :notified_events => %w(issue_added issue_updated) do - Repository.scan_changesets_for_issue_ids - end - 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.reorder('created_on desc').first - 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_not_nil mail - assert mail.subject.starts_with?( - "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]") - assert_mail_body_match( - "Status changed from #{old_status} to #{fixed_issue.status}", mail) - - # 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 - - def test_sort_should_not_raise_an_error_with_nil_identifiers - r1 = Repository.new - r2 = Repository.new - - assert_nothing_raised do - [r1, r2].sort - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c5d488c8c009bfaf1b3e16a81cc375cbb4e01e5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c5/c5d488c8c009bfaf1b3e16a81cc375cbb4e01e5b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 NewsControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments + + def setup + 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_index_with_invalid_project_should_respond_with_404 + get :index, :project_id => 999 + assert_response 404 + 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_should_show_attachments + attachment = Attachment.first + attachment.container = News.find(1) + attachment.save! + + get :show, :id => 1 + assert_response :success + assert_tag 'a', :content => attachment.filename + 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 + @request.session[:user_id] = 2 + + with_settings :notified_events => %w(news_added) do + post :create, :project_id => 1, :news => { :title => 'NewsControllerTest', + :description => 'This is the description', + :summary => '' } + end + 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_post_create_with_attachment + set_tmp_attachments_directory + @request.session[:user_id] = 2 + assert_difference 'News.count' do + assert_difference 'Attachment.count' do + post :create, :project_id => 1, + :news => { :title => 'Test', :description => 'This is the description' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + end + end + attachment = Attachment.first(:order => 'id DESC') + news = News.first(:order => 'id DESC') + assert_equal news, attachment.container + 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_error_tag :content => /title can't be blank/i + 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_put_update_with_attachment + set_tmp_attachments_directory + @request.session[:user_id] = 2 + assert_no_difference 'News.count' do + assert_difference 'Attachment.count' do + put :update, :id => 1, + :news => { :description => 'This is the description' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + end + end + attachment = Attachment.first(:order => 'id DESC') + assert_equal News.find(1), attachment.container + end + + def test_update_with_failure + @request.session[:user_id] = 2 + put :update, :id => 1, :news => { :description => '' } + assert_response :success + assert_template 'edit' + assert_error_tag :content => /description can't be blank/i + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c5/c5dd710a7f43b09dab39c8f1a38c740cf467e460.svn-base --- a/.svn/pristine/c5/c5dd710a7f43b09dab39c8f1a38c740cf467e460.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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', content_tag('option') + 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 -%> - "> - - - - - - - - - -<% 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) %> - <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> -
    -
    -

    <%= pagination_links_full @user_pages, @user_count %>

    - -<% html_title(l(:label_user_plural)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c620273a13fb0c5f40f2c943a9b0b5bb40f34369.svn-base --- a/.svn/pristine/c6/c620273a13fb0c5f40f2c943a9b0b5bb40f34369.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::TimeEntriesTest < Redmine::ApiTest::Base - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :time_entries - - def setup - Setting.rest_api_enabled = '1' - end - - context "GET /time_entries.xml" do - should "return time entries" do - get '/time_entries.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entries', - :child => {:tag => 'time_entry', :child => {:tag => 'id', :content => '2'}} - end - - context "with limit" do - should "return limited results" do - get '/time_entries.xml?limit=2', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entries', - :children => {:count => 2} - end - end - end - - context "GET /time_entries/2.xml" do - should "return requested time entry" do - get '/time_entries/2.xml', {}, credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'time_entry', - :child => {:tag => 'id', :content => '2'} - end - end - - context "POST /time_entries.xml" do - context "with issue_id" do - should "return create time entry" do - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'jsmith', entry.user.login - assert_equal Issue.find(1), entry.issue - assert_equal Project.find(1), entry.project - assert_equal Date.parse('2010-12-02'), entry.spent_on - assert_equal 3.5, entry.hours - assert_equal TimeEntryActivity.find(11), entry.activity - end - - should "accept custom fields" do - field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string') - - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => { - :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}] - }}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'accepted', entry.custom_field_value(field) - end - end - - context "with project_id" do - should "return create time entry" do - assert_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :created - assert_equal 'application/xml', @response.content_type - - entry = TimeEntry.first(:order => 'id DESC') - assert_equal 'jsmith', entry.user.login - assert_nil entry.issue - assert_equal Project.find(1), entry.project - assert_equal Date.parse('2010-12-02'), entry.spent_on - assert_equal 3.5, entry.hours - assert_equal TimeEntryActivity.find(11), entry.activity - end - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'TimeEntry.count' do - post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} - end - end - end - - context "PUT /time_entries/2.xml" do - context "with valid parameters" do - should "update time entry" do - assert_no_difference 'TimeEntry.count' do - put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_equal 'API Update', TimeEntry.find(2).comments - end - end - - context "with invalid parameters" do - should "return errors" do - assert_no_difference 'TimeEntry.count' do - put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - - assert_tag 'errors', :child => {:tag => 'error', :content => "Hours can't be blank"} - end - end - end - - context "DELETE /time_entries/2.xml" do - should "destroy time entry" do - assert_difference 'TimeEntry.count', -1 do - delete '/time_entries/2.xml', {}, credentials('jsmith') - end - assert_response :ok - assert_equal '', @response.body - assert_nil TimeEntry.find_by_id(2) - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c6259edf6d4a9d5a1738455be1808f3b0eaae4e1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c6259edf6d4a9d5a1738455be1808f3b0eaae4e1.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 FilesController < ApplicationController + menu_item :files + + before_filter :find_project_by_project_id + before_filter :authorize + + helper :sort + include SortHelper + + def index + sort_init 'filename', 'asc' + sort_update 'filename' => "#{Attachment.table_name}.filename", + 'created_on' => "#{Attachment.table_name}.created_on", + 'size' => "#{Attachment.table_name}.filesize", + 'downloads' => "#{Attachment.table_name}.downloads" + + @containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] + @containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse + render :layout => !request.xhr? + end + + def new + @versions = @project.versions.sort + end + + def create + container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) + attachments = Attachment.attach_files(container, params[:attachments]) + render_attachment_warning_if_needed(container) + + if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') + Mailer.attachments_added(attachments[:files]).deliver + end + redirect_to project_files_path(@project) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c65ce198718375cbe82249d0ec15351586b70868.svn-base --- a/.svn/pristine/c6/c65ce198718375cbe82249d0ec15351586b70868.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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.message_posted(message).deliver if Setting.notified_events.include?('message_posted') - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c66ed3b558cdd1c02b7c08d87594f47f1c08002f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c66ed3b558cdd1c02b7c08d87594f47f1c08002f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,322 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 IssuesCustomFieldsVisibilityTest < ActionController::TestCase + tests IssuesController + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issue_statuses, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :workflows + + def setup + CustomField.delete_all + Issue.delete_all + field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} + @fields = [] + @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) + @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) + @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) + @issue = Issue.generate!( + :author_id => 1, + :project_id => 1, + :tracker_id => 1, + :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} + ) + + @user_with_role_on_other_project = User.generate! + User.add_to_project(@user_with_role_on_other_project, Project.find(2), Role.find(3)) + + @users_to_test = { + User.find(1) => [@field1, @field2, @field3], + User.find(3) => [@field1, @field2], + @user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 + User.generate! => [@field1], + User.anonymous => [@field1] + } + + Member.where(:project_id => 1).each do |member| + member.destroy unless @users_to_test.keys.include?(member.principal) + end + end + + def test_show_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_api + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'custom_fields', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} in API" + else + assert_select "custom_field[id=#{field.id}] value", {:text => "Value#{i}", :count => 0}, "User #{user.id} was not able to view #{field.name} in API" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :show, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'ul.details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" + else + assert_select 'ul.details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" + end + end + end + end + + def test_show_should_show_visible_custom_fields_only_in_history_api + @issue.init_journal(User.find(1)) + @issue.custom_field_values = {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} + @issue.save! + + @users_to_test.each do |user, fields| + with_settings :rest_api_enabled => '1' do + get :show, :id => @issue.id, :format => 'xml', :include => 'journals', :key => user.api_key + end + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'details old_value', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change in API" + else + assert_select 'details old_value', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change in API" + end + end + end + end + + def test_edit_should_show_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :edit, :id => @issue.id + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'input[value=?]', "Value#{i}", 1, "User #{user.id} was not able to edit #{field.name}" + else + assert_select 'input[value=?]', "Value#{i}", 0, "User #{user.id} was able to edit #{field.name}" + end + end + end + end + + def test_update_should_update_visible_custom_fields_only + Role.anonymous.add_permission! :edit_issues + + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + put :update, :id => @issue.id, + :issue => {:custom_field_values => { + @field1.id.to_s => "User#{user.id}Value0", + @field2.id.to_s => "User#{user.id}Value1", + @field3.id.to_s => "User#{user.id}Value2", + }} + @issue.reload + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was not able to update #{field.name}" + else + assert_not_equal "User#{user.id}Value#{i}", @issue.custom_field_value(field), "User #{user.id} was able to update #{field.name}" + end + end + end + end + + def test_index_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}) + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_select 'td', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name}" + else + assert_select 'td', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name}" + end + end + end + end + + def test_index_as_csv_should_show_visible_custom_fields_only + @users_to_test.each do |user, fields| + @request.session[:user_id] = user.id + get :index, :c => (["subject"] + @fields.map{|f| "cf_#{f.id}"}), :format => 'csv' + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_include "Value#{i}", response.body, "User #{user.id} was not able to view #{field.name} in CSV" + else + assert_not_include "Value#{i}", response.body, "User #{user.id} was able to view #{field.name} in CSV" + end + end + end + end + + def test_index_with_partial_custom_field_visibility + Issue.delete_all + p1 = Project.generate! + p2 = Project.generate! + user = User.generate! + User.add_to_project(user, p1, Role.find_all_by_id(1,3)) + User.add_to_project(user, p2, Role.find_all_by_id(3)) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueA'}) + Issue.generate!(:project => p2, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueB'}) + Issue.generate!(:project => p1, :tracker_id => 1, :custom_field_values => {@field2.id => 'ValueC'}) + + @request.session[:user_id] = user.id + get :index, :c => ["subject", "cf_#{@field2.id}"] + assert_select 'td', :text => 'ValueA' + assert_select 'td', :text => 'ValueB', :count => 0 + assert_select 'td', :text => 'ValueC' + + get :index, :sort => "cf_#{@field2.id}" + # ValueB is not visible to user and ignored while sorting + assert_equal %w(ValueB ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + get :index, :set_filter => '1', "cf_#{@field2.id}" => '*' + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + + CustomField.update_all(:field_format => 'list') + get :index, :group => "cf_#{@field2.id}" + assert_equal %w(ValueA ValueC), assigns(:issues).map{|i| i.custom_field_value(@field2)} + end + + def test_create_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + assert_difference 'Issue.count' do + post :create, + :project_id => 1, + :issue => { + :tracker_id => 1, + :status_id => 1, + :subject => 'New issue', + :priority_id => 5, + :custom_field_values => {@field1.id.to_s => 'Value0', @field2.id.to_s => 'Value1', @field3.id.to_s => 'Value2'}, + :watcher_user_ids => users_to_test.keys.map(&:id) + } + assert_response 302 + end + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_update_should_send_notifications_according_custom_fields_visibility + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field1.id.to_s => 'NewValue0', @field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + assert_equal users_to_test.values.uniq.size, ActionMailer::Base.deliveries.size + # tests that each user receives 1 email with the custom fields he is allowed to see only + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + assert_equal 1, mails.size + mail = mails.first + @fields.each_with_index do |field, i| + if fields.include?(field) + assert_mail_body_match "Value#{i}", mail, "User #{user.id} was not able to view #{field.name} in notification" + else + assert_mail_body_no_match "Value#{i}", mail, "User #{user.id} was able to view #{field.name} in notification" + end + end + end + end + + def test_updating_hidden_custom_fields_only_should_not_notifiy_user + # anonymous user is never notified + users_to_test = @users_to_test.reject {|k,v| k.anonymous?} + + users_to_test.keys.each do |user| + Watcher.create!(:user => user, :watchable => @issue) + end + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 1 + with_settings :bcc_recipients => '1' do + put :update, + :id => @issue.id, + :issue => { + :custom_field_values => {@field2.id.to_s => 'NewValue1', @field3.id.to_s => 'NewValue2'} + } + assert_response 302 + end + users_to_test.each do |user, fields| + mails = ActionMailer::Base.deliveries.select {|m| m.bcc.include? user.mail} + if (fields & [@field2, @field3]).any? + assert_equal 1, mails.size, "User #{user.id} was not notified" + else + assert_equal 0, mails.size, "User #{user.id} was notified" + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c670b46ea2087eed9b8431a525368aa41fa88c54.svn-base --- a/.svn/pristine/c6/c670b46ea2087eed9b8431a525368aa41fa88c54.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'zlib' - -class WikiContent < ActiveRecord::Base - self.locking_column = 'version' - belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - validates_presence_of :text - validates_length_of :comments, :maximum => 255, :allow_nil => true - - acts_as_versioned - - def visible?(user=User.current) - page.visible?(user) - end - - def project - page.project - end - - def attachments - page.nil? ? [] : page.attachments - end - - # Returns the mail adresses of users that should be notified - def recipients - notified = project.notified_users - notified.reject! {|user| !visible?(user)} - notified.collect(&:mail) - end - - # Return true if the content is the current page content - def current_version? - true - end - - class Version - belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id' - belongs_to :author, :class_name => '::User', :foreign_key => 'author_id' - attr_protected :data - - acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, - :description => :comments, - :datetime => :updated_on, - :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', - :timestamp => "#{WikiContent.versioned_table_name}.updated_on", - :author_key => "#{WikiContent.versioned_table_name}.author_id", - :permission => :view_wiki_edits, - :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + - "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + - "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + - "#{WikiContent.versioned_table_name}.id", - :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + - "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + - "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} - - after_destroy :page_update_after_destroy - - def text=(plain) - case Setting.wiki_compression - when 'gzip' - begin - self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) - self.compression = 'gzip' - rescue - self.data = plain - self.compression = '' - end - else - self.data = plain - self.compression = '' - end - plain - end - - def text - @text ||= begin - str = case compression - when 'gzip' - Zlib::Inflate.inflate(data) - else - # uncompressed data - data - end - str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) - str - end - end - - def project - page.project - end - - # Return true if the content is the current page content - def current_version? - page.content.version == self.version - end - - # Returns the previous version or nil - def previous - @previous ||= WikiContent::Version. - reorder('version DESC'). - includes(:author). - where("wiki_content_id = ? AND version < ?", wiki_content_id, version).first - end - - # Returns the next version or nil - def next - @next ||= WikiContent::Version. - reorder('version ASC'). - includes(:author). - where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first - end - - private - - # Updates page's content if the latest version is removed - # or destroys the page if it was the only version - def page_update_after_destroy - latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first - if latest && page.content.version != latest.version - raise ActiveRecord::Rollback unless page.content.revert_to!(latest) - elsif latest.nil? - raise ActiveRecord::Rollback unless page.destroy - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c673436b013667ece4d8f947cdd3efc48718bb36.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c673436b013667ece4d8f947cdd3efc48718bb36.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c68a764ff663d1fa551848f69b2759490df8f986.svn-base --- a/.svn/pristine/c6/c68a764ff663d1fa551848f69b2759490df8f986.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,712 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - - # Account statuses - STATUS_ANONYMOUS = 0 - STATUS_ACTIVE = 1 - STATUS_REGISTERED = 2 - STATUS_LOCKED = 3 - - # 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, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}" - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {: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 lettres, numbers, underscores only - validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/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_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) - } - - 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 - - def reload(*args) - @name = nil - @projects_by_role = nil - super - 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 - # Invlaid 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 password - return nil if 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 - 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_attribute(: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) - 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 - 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) - # 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 - 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 - 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 - 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 - - # 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 = memberships.detect {|m| m.project_id == project.id} - 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) - !roles_for_project(project).detect {|role| role.member?}.nil? - 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) - case mail_notification - when '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)) - 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 - end - - def self.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.find(:first) - 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 - - # Anonymous user can not be destroyed - def destroy - false - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c694dfda8151ff2b41dbc5035ac34e1f8ed39803.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c694dfda8151ff2b41dbc5035ac34e1f8ed39803.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,62 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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/pop' + +module Redmine + module POP3 + class << self + def check(pop_options={}, options={}) + host = pop_options[:host] || '127.0.0.1' + port = pop_options[:port] || '110' + apop = (pop_options[:apop].to_s == '1') + delete_unprocessed = (pop_options[:delete_unprocessed].to_s == '1') + + pop = Net::POP3.APOP(apop).new(host,port) + logger.debug "Connecting to #{host}..." if logger && logger.debug? + pop.start(pop_options[:username], pop_options[:password]) do |pop_session| + if pop_session.mails.empty? + logger.debug "No email to process" if logger && logger.debug? + else + logger.debug "#{pop_session.mails.size} email(s) to process..." if logger && logger.debug? + pop_session.each_mail do |msg| + message = msg.pop + message_id = (message =~ /^Message-I[dD]: (.*)/ ? $1 : '').strip + if MailHandler.receive(message, options) + msg.delete + logger.debug "--> Message #{message_id} processed and deleted from the server" if logger && logger.debug? + else + if delete_unprocessed + msg.delete + logger.debug "--> Message #{message_id} NOT processed and deleted from the server" if logger && logger.debug? + else + logger.debug "--> Message #{message_id} NOT processed and left on the server" if logger && logger.debug? + end + end + end + end + end + end + + private + + def logger + ::Rails.logger + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c69b9e953e7f9b3d22f5c668c4e090860e04cf57.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c6/c69b9e953e7f9b3d22f5c668c4e090860e04cf57.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,28 @@ += 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 + += License + +Copyright (C) 2006-2014 Jean-Philippe Lang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Icons credits: + +* Mark James (Silk Icons) licensed under a Creative Commons Attribution 2.5 License. +* Yusuke Kamiyamane (Fugue Icons) licensed under a Creative Commons Attribution 3.0 License. diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c6/c6d9ab86ea1cdaba4e340d8bf5228b6e3d11c2ff.svn-base --- a/.svn/pristine/c6/c6d9ab86ea1cdaba4e340d8bf5228b6e3d11c2ff.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 :controller => 'news', :action => 'show', :id => @news - end - - def destroy - @news.comments.find(params[:comment_id]).destroy - redirect_to :controller => 'news', :action => 'show', :id => @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c7/c76b4386c82424294b50c9641cdeb22f3fba841c.svn-base --- a/.svn/pristine/c7/c76b4386c82424294b50c9641cdeb22f3fba841c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,183 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 ProjectNestedSetTest < ActiveSupport::TestCase - - def setup - Project.delete_all - - @a = Project.create!(:name => 'A', :identifier => 'projecta') - @a1 = Project.create!(:name => 'A1', :identifier => 'projecta1') - @a1.set_parent!(@a) - @a2 = Project.create!(:name => 'A2', :identifier => 'projecta2') - @a2.set_parent!(@a) - - @c = Project.create!(:name => 'C', :identifier => 'projectc') - @c1 = Project.create!(:name => 'C1', :identifier => 'projectc1') - @c1.set_parent!(@c) - - @b = Project.create!(:name => 'B', :identifier => 'projectb') - @b2 = Project.create!(:name => 'B2', :identifier => 'projectb2') - @b2.set_parent!(@b) - @b1 = Project.create!(:name => 'B1', :identifier => 'projectb1') - @b1.set_parent!(@b) - @b11 = Project.create!(:name => 'B11', :identifier => 'projectb11') - @b11.set_parent!(@b1) - - @a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1 = *(Project.all.sort_by(&:name)) - end - - def test_valid_tree - assert_valid_nested_set - end - - def test_rebuild_should_build_valid_tree - Project.update_all "lft = NULL, rgt = NULL" - - Project.rebuild! - assert_valid_nested_set - end - - def test_rebuild_tree_should_build_valid_tree_even_with_valid_lft_rgt_values - Project.update_all "name = 'YY'", {:id => @a.id } - # lft and rgt values are still valid (Project.rebuild! would not update anything) - # but projects are not ordered properly (YY is in the first place) - - Project.rebuild_tree! - assert_valid_nested_set - end - - def test_moving_a_child_to_a_different_parent_should_keep_valid_tree - assert_no_difference 'Project.count' do - Project.find_by_name('B1').set_parent!(Project.find_by_name('A2')) - end - assert_valid_nested_set - end - - def test_renaming_a_root_to_first_position_should_update_nested_set_order - @c.name = '1' - @c.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_middle_position_should_update_nested_set_order - @a.name = 'BA' - @a.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_last_position_should_update_nested_set_order - @a.name = 'D' - @a.save! - assert_valid_nested_set - end - - def test_renaming_a_root_to_same_position_should_update_nested_set_order - @c.name = 'D' - @c.save! - assert_valid_nested_set - end - - def test_renaming_a_child_should_update_nested_set_order - @a1.name = 'A3' - @a1.save! - assert_valid_nested_set - end - - def test_renaming_a_child_with_child_should_update_nested_set_order - @b1.name = 'B3' - @b1.save! - assert_valid_nested_set - end - - def test_adding_a_root_to_first_position_should_update_nested_set_order - project = Project.create!(:name => '1', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_adding_a_root_to_middle_position_should_update_nested_set_order - project = Project.create!(:name => 'BA', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_adding_a_root_to_last_position_should_update_nested_set_order - project = Project.create!(:name => 'Z', :identifier => 'projectba') - assert_valid_nested_set - end - - def test_destroying_a_root_with_children_should_keep_valid_tree - assert_difference 'Project.count', -4 do - Project.find_by_name('B').destroy - end - assert_valid_nested_set - end - - def test_destroying_a_child_with_children_should_keep_valid_tree - assert_difference 'Project.count', -2 do - Project.find_by_name('B1').destroy - end - assert_valid_nested_set - end - - private - - def assert_nested_set_values(h) - assert Project.valid? - h.each do |project, expected| - project.reload - assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}" - end - end - - def assert_valid_nested_set - projects = Project.all - lft_rgt = projects.map {|p| [p.lft, p.rgt]}.flatten - assert_equal projects.size * 2, lft_rgt.uniq.size - assert_equal 1, lft_rgt.min - assert_equal projects.size * 2, lft_rgt.max - - projects.each do |project| - # lft should always be < rgt - assert project.lft < project.rgt, "lft=#{project.lft} was not < rgt=#{project.rgt} for project #{project.name}" - if project.parent_id - # child lft/rgt values must be greater/lower - assert_not_nil project.parent, "parent was nil for project #{project.name}" - assert project.lft > project.parent.lft, "lft=#{project.lft} was not > parent.lft=#{project.parent.lft} for project #{project.name}" - assert project.rgt < project.parent.rgt, "rgt=#{project.rgt} was not < parent.rgt=#{project.parent.rgt} for project #{project.name}" - end - # no overlapping lft/rgt values - overlapping = projects.detect {|other| - other != project && ( - (other.lft > project.lft && other.lft < project.rgt && other.rgt > project.rgt) || - (other.rgt > project.lft && other.rgt < project.rgt && other.lft < project.lft) - ) - } - assert_nil overlapping, (overlapping && "Project #{overlapping.name} (#{overlapping.lft}/#{overlapping.rgt}) overlapped #{project.name} (#{project.lft}/#{project.rgt})") - end - - # root projects sorted alphabetically - assert_equal Project.roots.map(&:name).sort, Project.roots.sort_by(&:lft).map(&:name), "Root projects were not properly sorted" - projects.each do |project| - if project.children.any? - # sibling projects sorted alphabetically - assert_equal project.children.map(&:name).sort, project.children.order('lft').map(&:name), "Project #{project.name}'s children were not properly sorted" - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c7/c7caca3b76ded3c7cd983503d6e3ad0588e89467.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c7/c7caca3b76ded3c7cd983503d6e3ad0588e89467.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,64 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c7/c7d848a2c73159ca5a8ab924fb977b51dbc69f9f.svn-base --- a/.svn/pristine/c7/c7d848a2c73159ca5a8ab924fb977b51dbc69f9f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,937 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 ProjectTest < 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 - @ecookbook = Project.find(1) - @ecookbook_sub1 = Project.find(3) - set_tmp_attachments_directory - User.current = nil - end - - def test_truth - assert_kind_of Project, @ecookbook - assert_equal "eCookbook", @ecookbook.name - end - - def test_default_attributes - with_settings :default_projects_public => '1' do - assert_equal true, Project.new.is_public - assert_equal false, Project.new(:is_public => false).is_public - end - - with_settings :default_projects_public => '0' do - assert_equal false, Project.new.is_public - assert_equal true, Project.new(:is_public => true).is_public - end - - with_settings :sequential_project_identifiers => '1' do - assert !Project.new.identifier.blank? - assert Project.new(:identifier => '').identifier.blank? - end - - with_settings :sequential_project_identifiers => '0' do - assert Project.new.identifier.blank? - assert !Project.new(:identifier => 'test').blank? - end - - with_settings :default_projects_modules => ['issue_tracking', 'repository'] do - assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names - end - end - - 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 - assert_equal "eCookbook", @ecookbook.name - @ecookbook.name = "eCook" - assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ") - @ecookbook.reload - assert_equal "eCook", @ecookbook.name - end - - def test_validate_identifier - to_test = {"abc" => true, - "ab12" => true, - "ab-12" => true, - "ab_12" => true, - "12" => false, - "new" => false} - - to_test.each do |identifier, valid| - p = Project.new - p.identifier = identifier - p.valid? - if valid - assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid" - else - assert p.errors['identifier'].present?, "identifier #{identifier} was valid" - end - end - end - - def test_identifier_should_not_be_frozen_for_a_new_project - assert_equal false, Project.new.identifier_frozen? - end - - def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier - Project.update_all(["identifier = ''"], "id = 1") - - assert_equal false, Project.find(1).identifier_frozen? - end - - def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier - assert_equal true, Project.find(1).identifier_frozen? - end - - def test_members_should_be_active_users - Project.all.each do |project| - assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) } - end - end - - def test_users_should_be_active_users - Project.all.each do |project| - assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) } - end - end - - def test_open_scope_on_issues_association - assert_kind_of Issue, Project.find(1).issues.open.first - end - - def test_archive - user = @ecookbook.members.first.user - @ecookbook.archive - @ecookbook.reload - - assert !@ecookbook.active? - assert @ecookbook.archived? - assert !user.projects.include?(@ecookbook) - # Subproject are also archived - assert !@ecookbook.children.empty? - assert @ecookbook.descendants.active.empty? - end - - def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects - # Assign an issue of a project to a version of a child project - Issue.find(4).update_attribute :fixed_version_id, 4 - - assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do - assert_equal false, @ecookbook.archive - end - @ecookbook.reload - assert @ecookbook.active? - end - - def test_unarchive - user = @ecookbook.members.first.user - @ecookbook.archive - # A subproject of an archived project can not be unarchived - assert !@ecookbook_sub1.unarchive - - # Unarchive project - assert @ecookbook.unarchive - @ecookbook.reload - assert @ecookbook.active? - assert !@ecookbook.archived? - assert user.projects.include?(@ecookbook) - # Subproject can now be unarchived - @ecookbook_sub1.reload - assert @ecookbook_sub1.unarchive - end - - def test_destroy - # 2 active members - assert_equal 2, @ecookbook.members.size - # and 1 is locked - assert_equal 3, Member.where('project_id = ?', @ecookbook.id).all.size - # some boards - assert @ecookbook.boards.any? - - @ecookbook.destroy - # make sure that the project non longer exists - assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) } - # make sure related data was removed - assert_nil Member.first(:conditions => {:project_id => @ecookbook.id}) - assert_nil Board.first(:conditions => {:project_id => @ecookbook.id}) - assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id}) - end - - def test_destroy_should_destroy_subtasks - issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')} - issues[0].update_attribute :parent_issue_id, issues[1].id - issues[2].update_attribute :parent_issue_id, issues[1].id - assert_equal 2, issues[1].children.count - - assert_nothing_raised do - Project.find(1).destroy - end - assert Issue.find_all_by_id(issues.map(&:id)).empty? - end - - def test_destroying_root_projects_should_clear_data - Project.roots.each do |root| - root.destroy - end - - assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}" - assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}" - assert_equal 0, MemberRole.count - assert_equal 0, Issue.count - assert_equal 0, Journal.count - assert_equal 0, JournalDetail.count - assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}" - assert_equal 0, EnabledModule.count - assert_equal 0, IssueCategory.count - assert_equal 0, IssueRelation.count - assert_equal 0, Board.count - assert_equal 0, Message.count - assert_equal 0, News.count - assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL") - assert_equal 0, Repository.count - assert_equal 0, Changeset.count - assert_equal 0, Change.count - assert_equal 0, Comment.count - assert_equal 0, TimeEntry.count - assert_equal 0, Version.count - assert_equal 0, Watcher.count - assert_equal 0, Wiki.count - assert_equal 0, WikiPage.count - assert_equal 0, WikiContent.count - assert_equal 0, WikiContent::Version.count - assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size - assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size - assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']}) - end - - def test_move_an_orphan_project_to_a_root_project - sub = Project.find(2) - sub.set_parent! @ecookbook - assert_equal @ecookbook.id, sub.parent.id - @ecookbook.reload - assert_equal 4, @ecookbook.children.size - end - - def test_move_an_orphan_project_to_a_subproject - sub = Project.find(2) - assert sub.set_parent!(@ecookbook_sub1) - end - - def test_move_a_root_project_to_a_project - sub = @ecookbook - assert sub.set_parent!(Project.find(2)) - end - - def test_should_not_move_a_project_to_its_children - sub = @ecookbook - assert !(sub.set_parent!(Project.find(3))) - end - - def test_set_parent_should_add_roots_in_alphabetical_order - ProjectCustomField.delete_all - Project.delete_all - Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil) - Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil) - Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil) - Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil) - - assert_equal 4, Project.count - assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft) - end - - def test_set_parent_should_add_children_in_alphabetical_order - ProjectCustomField.delete_all - parent = Project.create!(:name => 'Parent', :identifier => 'parent') - Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent) - Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent) - Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent) - Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent) - - parent.reload - assert_equal 4, parent.children.size - assert_equal parent.children.all.sort_by(&:name), parent.children.all - end - - def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy - # Parent issue with a hierarchy project's fixed version - parent_issue = Issue.find(1) - parent_issue.update_attribute(:fixed_version_id, 4) - parent_issue.reload - assert_equal 4, parent_issue.fixed_version_id - - # Should keep fixed versions for the issues - issue_with_local_fixed_version = Issue.find(5) - issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4) - issue_with_local_fixed_version.reload - assert_equal 4, issue_with_local_fixed_version.fixed_version_id - - # Local issue with hierarchy fixed_version - issue_with_hierarchy_fixed_version = Issue.find(13) - issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6) - issue_with_hierarchy_fixed_version.reload - assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id - - # Move project out of the issue's hierarchy - moved_project = Project.find(3) - moved_project.set_parent!(Project.find(2)) - parent_issue.reload - issue_with_local_fixed_version.reload - issue_with_hierarchy_fixed_version.reload - - assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project" - assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in" - assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue." - end - - def test_parent - p = Project.find(6).parent - assert p.is_a?(Project) - assert_equal 5, p.id - end - - def test_ancestors - a = Project.find(6).ancestors - assert a.first.is_a?(Project) - assert_equal [1, 5], a.collect(&:id) - end - - def test_root - r = Project.find(6).root - assert r.is_a?(Project) - assert_equal 1, r.id - end - - def test_children - c = Project.find(1).children - assert c.first.is_a?(Project) - assert_equal [5, 3, 4], c.collect(&:id) - end - - def test_descendants - d = Project.find(1).descendants - assert d.first.is_a?(Project) - assert_equal [5, 6, 3, 4], d.collect(&:id) - end - - def test_allowed_parents_should_be_empty_for_non_member_user - Role.non_member.add_permission!(:add_project) - user = User.find(9) - assert user.memberships.empty? - User.current = user - assert Project.new.allowed_parents.compact.empty? - end - - def test_allowed_parents_with_add_subprojects_permission - Role.find(1).remove_permission!(:add_project) - Role.find(1).add_permission!(:add_subprojects) - User.current = User.find(2) - # new project - assert !Project.new.allowed_parents.include?(nil) - assert Project.new.allowed_parents.include?(Project.find(1)) - # existing root project - assert Project.find(1).allowed_parents.include?(nil) - # existing child - assert Project.find(3).allowed_parents.include?(Project.find(1)) - assert !Project.find(3).allowed_parents.include?(nil) - end - - def test_allowed_parents_with_add_project_permission - Role.find(1).add_permission!(:add_project) - Role.find(1).remove_permission!(:add_subprojects) - User.current = User.find(2) - # new project - assert Project.new.allowed_parents.include?(nil) - assert !Project.new.allowed_parents.include?(Project.find(1)) - # existing root project - assert Project.find(1).allowed_parents.include?(nil) - # existing child - assert Project.find(3).allowed_parents.include?(Project.find(1)) - assert Project.find(3).allowed_parents.include?(nil) - end - - def test_allowed_parents_with_add_project_and_subprojects_permission - Role.find(1).add_permission!(:add_project) - Role.find(1).add_permission!(:add_subprojects) - User.current = User.find(2) - # new project - assert Project.new.allowed_parents.include?(nil) - assert Project.new.allowed_parents.include?(Project.find(1)) - # existing root project - assert Project.find(1).allowed_parents.include?(nil) - # existing child - assert Project.find(3).allowed_parents.include?(Project.find(1)) - assert Project.find(3).allowed_parents.include?(nil) - end - - def test_users_by_role - users_by_role = Project.find(1).users_by_role - assert_kind_of Hash, users_by_role - role = Role.find(1) - assert_kind_of Array, users_by_role[role] - assert users_by_role[role].include?(User.find(2)) - end - - def test_rolled_up_trackers - parent = Project.find(1) - parent.trackers = Tracker.find([1,2]) - child = parent.children.find(3) - - assert_equal [1, 2], parent.tracker_ids - assert_equal [2, 3], child.trackers.collect(&:id) - - assert_kind_of Tracker, parent.rolled_up_trackers.first - assert_equal Tracker.find(1), parent.rolled_up_trackers.first - - assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id) - assert_equal [2, 3], child.rolled_up_trackers.collect(&:id) - end - - def test_rolled_up_trackers_should_ignore_archived_subprojects - parent = Project.find(1) - parent.trackers = Tracker.find([1,2]) - child = parent.children.find(3) - child.trackers = Tracker.find([1,3]) - parent.children.each(&:archive) - - assert_equal [1,2], parent.rolled_up_trackers.collect(&:id) - end - - 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]) - - assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort - - assert child.disable_module!(:issue_tracking) - parent.reload - assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort - 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 - - 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) - - assert_same_elements [ - parent_version_1, - parent_version_2, - 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 - - assert_same_elements [ - parent_version_1, - parent_version_2, - sub_subproject_version - ], project.rolled_up_versions - end - - 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 - - def test_shared_versions_none_sharing - p = Project.find(5) - v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none') - assert p.shared_versions.include?(v) - assert !p.children.first.shared_versions.include?(v) - assert !p.root.shared_versions.include?(v) - assert !p.siblings.first.shared_versions.include?(v) - assert !p.root.siblings.first.shared_versions.include?(v) - end - - def test_shared_versions_descendants_sharing - p = Project.find(5) - v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants') - assert p.shared_versions.include?(v) - assert p.children.first.shared_versions.include?(v) - assert !p.root.shared_versions.include?(v) - assert !p.siblings.first.shared_versions.include?(v) - assert !p.root.siblings.first.shared_versions.include?(v) - end - - def test_shared_versions_hierarchy_sharing - p = Project.find(5) - v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy') - assert p.shared_versions.include?(v) - assert p.children.first.shared_versions.include?(v) - assert p.root.shared_versions.include?(v) - assert !p.siblings.first.shared_versions.include?(v) - assert !p.root.siblings.first.shared_versions.include?(v) - end - - def test_shared_versions_tree_sharing - p = Project.find(5) - v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree') - assert p.shared_versions.include?(v) - assert p.children.first.shared_versions.include?(v) - assert p.root.shared_versions.include?(v) - assert p.siblings.first.shared_versions.include?(v) - assert !p.root.siblings.first.shared_versions.include?(v) - end - - def test_shared_versions_system_sharing - p = Project.find(5) - v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system') - assert p.shared_versions.include?(v) - assert p.children.first.shared_versions.include?(v) - assert p.root.shared_versions.include?(v) - assert p.siblings.first.shared_versions.include?(v) - assert p.root.siblings.first.shared_versions.include?(v) - end - - def test_shared_versions - parent = Project.find(1) - child = parent.children.find(3) - private_child = parent.children.find(5) - - assert_equal [1,2,3], parent.version_ids.sort - assert_equal [4], child.version_ids - assert_equal [6], private_child.version_ids - assert_equal [7], Version.find_all_by_sharing('system').collect(&:id) - - assert_equal 6, parent.shared_versions.size - parent.shared_versions.each do |version| - assert_kind_of Version, version - end - - assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort - end - - def test_shared_versions_should_ignore_archived_subprojects - parent = Project.find(1) - child = parent.children.find(3) - child.archive - parent.reload - - assert_equal [1,2,3], parent.version_ids.sort - assert_equal [4], child.version_ids - assert !parent.shared_versions.collect(&:id).include?(4) - end - - def test_shared_versions_visible_to_user - user = User.find(3) - parent = Project.find(1) - child = parent.children.find(5) - - assert_equal [1,2,3], parent.version_ids.sort - assert_equal [6], child.version_ids - - versions = parent.shared_versions.visible(user) - - assert_equal 4, versions.size - versions.each do |version| - assert_kind_of Version, version - end - - assert !versions.collect(&:id).include?(6) - end - - def test_shared_versions_for_new_project_should_include_system_shared_versions - p = Project.find(5) - v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system') - - assert_include v, Project.new.shared_versions - end - - def test_next_identifier - ProjectCustomField.delete_all - Project.create!(:name => 'last', :identifier => 'p2008040') - assert_equal 'p2008041', Project.next_identifier - end - - def test_next_identifier_first_project - Project.delete_all - assert_nil Project.next_identifier - end - - def test_enabled_module_names - with_settings :default_projects_modules => ['issue_tracking', 'repository'] do - project = Project.new - - project.enabled_module_names = %w(issue_tracking news) - assert_equal %w(issue_tracking news), project.enabled_module_names.sort - end - end - - 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 - - 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 - - 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 - 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 - - def test_copy_from_existing_project - source_project = Project.find(1) - copied_project = Project.copy_from(1) - - assert copied_project - # Cleared attributes - assert copied_project.id.blank? - assert copied_project.name.blank? - assert copied_project.identifier.blank? - - # Duplicated attributes - assert_equal source_project.description, copied_project.description - assert_equal source_project.enabled_modules, copied_project.enabled_modules - assert_equal source_project.trackers, copied_project.trackers - - # Default attributes - assert_equal 1, copied_project.status - end - - def test_activities_should_use_the_system_activities - project = Project.find(1) - assert_equal project.activities, TimeEntryActivity.where(:active => true).all - end - - - def test_activities_should_use_the_project_specific_activities - project = Project.find(1) - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project}) - assert overridden_activity.save! - - assert project.activities.include?(overridden_activity), "Project specific Activity not found" - end - - 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.first, :active => false}) - assert overridden_activity.save! - - assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found" - end - - def test_activities_should_not_include_project_specific_activities_from_other_projects - project = Project.find(1) - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)}) - assert overridden_activity.save! - - assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project" - end - - def test_activities_should_handle_nils - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first}) - TimeEntryActivity.delete_all - - # No activities - project = Project.find(1) - assert project.activities.empty? - - # No system, one overridden - assert overridden_activity.save! - project.reload - assert_equal [overridden_activity], project.activities - end - - def test_activities_should_override_system_activities_with_project_activities - project = Project.find(1) - parent_activity = TimeEntryActivity.first - overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity}) - assert overridden_activity.save! - - assert project.activities.include?(overridden_activity), "Project specific Activity not found" - assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden" - end - - def test_activities_should_include_inactive_activities_if_specified - project = Project.find(1) - 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" - end - - test 'activities should not include active System activities if the project has an override that is inactive' do - project = Project.find(1) - system_activity = TimeEntryActivity.find_by_name('Design') - assert system_activity.active? - overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false) - assert overridden_activity.save! - - assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found" - assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override" - end - - def test_close_completed_versions - Version.update_all("status = 'open'") - project = Project.find(1) - assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'} - assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'} - project.close_completed_versions - project.reload - assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'} - assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'} - 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 - - 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_nil project.start_date - 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) - - assert_equal early, project.start_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 - - 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) - - assert_nil project.due_date - end - - 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) - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c7/c7e95199fb385468769aedf565c2d839f1f86093.svn-base --- a/.svn/pristine/c7/c7e95199fb385468769aedf565c2d839f1f86093.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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] - validate :validate_user - - # 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.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user| - pruned += prune_single_user(user, options) - end - pruned - end - end - - protected - - def validate_user - 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 - where(:user_id => user.id).all.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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c7/c7f45701bf3ca95a0ce3920f446037ebb4fbe0a7.svn-base --- a/.svn/pristine/c7/c7f45701bf3ca95a0ce3920f446037ebb4fbe0a7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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" - javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript url}'); 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-textile.min') + - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c8/c8161dc18d368b5a9f7435cd9c889d8ea63a8a29.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c8161dc18d368b5a9f7435cd9c889d8ea63a8a29.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,252 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'diff' +require 'enumerator' + +class WikiPage < ActiveRecord::Base + include Redmine::SafeAttributes + + belongs_to :wiki + has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy + acts_as_attachable :delete_permission => :delete_wiki_pages_attachments + acts_as_tree :dependent => :nullify, :order => 'title' + + acts_as_watchable + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"}, + :description => :text, + :datetime => :created_on, + :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}} + + acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"], + :include => [{:wiki => :project}, :content], + :permission => :view_wiki_pages, + :project_key => "#{Wiki.table_name}.project_id" + + attr_accessor :redirect_existing_links + + validates_presence_of :title + validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/ + validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false + validates_associated :content + + validate :validate_parent_title + before_destroy :remove_redirects + before_save :handle_redirects + + # eager load information about last updates, without loading text + 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 + DEFAULT_PROTECTED_PAGES = %w(sidebar) + + safe_attributes 'parent_id', 'parent_title', + :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)} + + def initialize(attributes=nil, *args) + super + if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase) + self.protected = true + end + end + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_wiki_pages, project) + end + + def title=(value) + value = Wiki.titleize(value) + @previous_title = read_attribute(:title) if @previous_title.blank? + write_attribute(:title, value) + end + + def handle_redirects + self.title = Wiki.titleize(title) + # Manage redirects if the title has changed + if !@previous_title.blank? && (@previous_title != title) && !new_record? + # Update redirects that point to the old title + wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r| + r.redirects_to = title + r.title == r.redirects_to ? r.destroy : r.save + end + # Remove redirects for the new title + wiki.redirects.find_all_by_title(title).each(&:destroy) + # Create a redirect to the new title + wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0" + @previous_title = nil + end + end + + def remove_redirects + # Remove redirects to this page + wiki.redirects.find_all_by_redirects_to(title).each(&:destroy) + end + + def pretty_title + WikiPage.pretty_title(title) + end + + def content_for_version(version=nil) + result = content.versions.find_by_version(version.to_i) if version + result ||= content + result + end + + def diff(version_to=nil, version_from=nil) + version_to = version_to ? version_to.to_i : self.content.version + content_to = content.versions.find_by_version(version_to) + content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous) + return nil unless content_to && content_from + + if content_from.version > content_to.version + content_to, content_from = content_from, content_to + end + + (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil + end + + def annotate(version=nil) + version = version ? version.to_i : self.content.version + c = content.versions.find_by_version(version) + c ? WikiAnnotate.new(c) : nil + end + + def self.pretty_title(str) + (str && str.is_a?(String)) ? str.tr('_', ' ') : str + end + + def project + wiki.project + end + + def text + content.text if content + end + + def updated_on + unless @updated_on + if time = read_attribute(:updated_on) + # content updated_on was eager loaded with the page + begin + @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime) + rescue + end + else + @updated_on = content && content.updated_on + end + end + @updated_on + end + + # Returns true if usr is allowed to edit the page, otherwise false + def editable_by?(usr) + !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) + end + + def attachments_deletable?(usr=User.current) + editable_by?(usr) && super(usr) + end + + def parent_title + @parent_title || (self.parent && self.parent.pretty_title) + end + + def parent_title=(t) + @parent_title = t + parent_page = t.blank? ? nil : self.wiki.find_page(t) + self.parent = parent_page + end + + # Saves the page and its content if text was changed + def save_with_content(content) + ret = nil + transaction do + self.content = content + if new_record? + # Rails automatically saves associated content + ret = save + else + ret = save && (content.text_changed? ? content.save : true) + end + raise ActiveRecord::Rollback unless ret + end + ret + end + + protected + + def validate_parent_title + errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil? + errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self)) + errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id) + end +end + +class WikiDiff < Redmine::Helpers::Diff + attr_reader :content_to, :content_from + + def initialize(content_to, content_from) + @content_to = content_to + @content_from = content_from + super(content_to.text, content_from.text) + end +end + +class WikiAnnotate + attr_reader :lines, :content + + def initialize(content) + @content = content + current = content + current_lines = current.text.split(/\r?\n/) + @lines = current_lines.collect {|t| [nil, nil, t]} + positions = [] + current_lines.size.times {|i| positions << i} + while (current.previous) + d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten + d.each_slice(3) do |s| + sign, line = s[0], s[1] + if sign == '+' && positions[line] && positions[line] != -1 + if @lines[positions[line]][0].nil? + @lines[positions[line]][0] = current.version + @lines[positions[line]][1] = current.author + end + end + end + d.each_slice(3) do |s| + sign, line = s[0], s[1] + if sign == '-' + positions.insert(line, -1) + else + positions[line] = nil + end + end + positions.compact! + # Stop if every line is annotated + break unless @lines.detect { |line| line[0].nil? } + current = current.previous + end + @lines.each { |line| + line[0] ||= current.version + # if the last known version is > 1 (eg. history was cleared), we don't know the author + line[1] ||= current.author if current.version == 1 + } + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c8/c851d242d762b6f733cca62cd2f74278f083ce0f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c851d242d762b6f733cca62cd2f74278f083ce0f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,33 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c8/c86b179f5d130043d8c3b043bc12c22e16c1356d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/c8/c86b179f5d130043d8c3b043bc12c22e16c1356d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,437 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + attr_reader :menu, :menu_items + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c8/c8ead97161810444d935cbc0189f12e56246f822.svn-base --- a/.svn/pristine/c8/c8ead97161810444d935cbc0189f12e56246f822.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,494 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -#require 'shoulda' -ENV["RAILS_ENV"] = "test" -require File.expand_path(File.dirname(__FILE__) + "/../config/environment") -require 'rails/test_help' -require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s - -require File.expand_path(File.dirname(__FILE__) + '/object_helpers') -include ObjectHelpers - -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" - assert_equal nil, session[:user_id] - assert_response :success - assert_template "account/login" - post "/login", :username => login, :password => password - assert_equal login, User.find(session[:user_id]).login - end - - def uploaded_test_file(name, mime) - fixture_file_upload("files/#{name}", mime, true) - end - - def credentials(user, password=nil) - {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)} - end - - # Mock out a file - def self.mock_file - file = 'a_file.png' - file.stubs(:size).returns(32) - file.stubs(:original_filename).returns('a_file.png') - file.stubs(:content_type).returns('image/png') - file.stubs(:read).returns(false) - file - end - - def mock_file - self.class.mock_file - end - - def mock_file_with_options(options={}) - file = '' - file.stubs(:size).returns(32) - original_filename = options[:original_filename] || nil - file.stubs(:original_filename).returns(original_filename) - content_type = options[:content_type] || nil - file.stubs(:content_type).returns(content_type) - file.stubs(:read).returns(false) - file - end - - # Use a temporary directory for attachment related tests - def set_tmp_attachments_directory - Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test") - unless File.directory?("#{Rails.root}/tmp/test/attachments") - Dir.mkdir "#{Rails.root}/tmp/test/attachments" - end - Attachment.storage_path = "#{Rails.root}/tmp/test/attachments" - end - - def set_fixtures_attachments_directory - Attachment.storage_path = "#{Rails.root}/test/fixtures/files" - 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} - options.each {|k, v| Setting[k] = v} - yield - ensure - saved_settings.each {|k, v| Setting[k] = v} if saved_settings - end - - # Yields the block with user as the current user - def with_current_user(user, &block) - saved_user = User.current - User.current = user - yield - ensure - User.current = saved_user - end - - def change_user_password(login, new_password) - user = User.first(:conditions => {:login => login}) - user.password, user.password_confirmation = new_password, new_password - user.save! - end - - def self.ldap_configured? - @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389) - return @test_ldap.bind - rescue Exception => e - # LDAP is not listening - return nil - end - - def self.convert_installed? - Redmine::Thumbnail.convert_available? - end - - # Returns the path to the test +vendor+ repository - def self.repository_path(vendor) - Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s - end - - # Returns the url of the subversion test repository - def self.subversion_repository_url - path = repository_path('subversion') - path = '/' + path unless path.starts_with?('/') - "file://#{path}" - end - - # Returns true if the +vendor+ test repository is configured - def self.repository_configured?(vendor) - File.directory?(repository_path(vendor)) - end - - def repository_path_hash(arr) - hs = {} - hs[:path] = arr.join("/") - hs[:param] = arr.join("/") - hs - end - - def assert_save(object) - saved = object.save - message = "#{object.class} could not be saved" - errors = object.errors.full_messages.map {|m| "- #{m}"} - message << ":\n#{errors.join("\n")}" if errors.any? - assert_equal true, saved, message - end - - def assert_error_tag(options={}) - assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options)) - end - - def assert_include(expected, s, message=nil) - assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"") - end - - def assert_not_include(expected, s) - assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\"" - end - - def assert_select_in(text, *args, &block) - d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root - assert_select(d, *args, &block) - end - - def assert_mail_body_match(expected, mail) - if expected.is_a?(String) - assert_include expected, mail_body(mail) - else - assert_match expected, mail_body(mail) - end - end - - def assert_mail_body_no_match(expected, mail) - if expected.is_a?(String) - assert_not_include expected, mail_body(mail) - else - assert_no_match expected, mail_body(mail) - end - end - - def mail_body(mail) - mail.parts.first.body.encoded - 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' - 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 - - 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 - - # 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 - end -end - -# Simple module to "namespace" all of the API tests -module ApiTest -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c9/c915cdc5a63346a4f09f2d9cd1c727112cb89f1c.svn-base --- a/.svn/pristine/c9/c915cdc5a63346a4f09f2d9cd1c727112cb89f1c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,371 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::UsersTest < Redmine::ApiTest::Base - fixtures :users, :members, :member_roles, :roles, :projects - - 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_response :success - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end - - context "with include=memberships" do - should "include memberships" do - get '/users/2.xml?include=memberships' - - assert_response :success - assert_tag :tag => 'memberships', - :parent => {:tag => 'user'}, - :children => {:count => 1} - end - end - end - - context ".json" do - should "return requested user" do - get '/users/2.json' - - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['user'] - assert_equal 2, json['user']['id'] - end - - context "with include=memberships" do - should "include memberships" do - get '/users/2.json?include=memberships' - - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Array, json['user']['memberships'] - assert_equal [{ - "id"=>1, - "project"=>{"name"=>"eCookbook", "id"=>1}, - "roles"=>[{"name"=>"Manager", "id"=>1}] - }], json['user']['memberships'] - end - 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', {}, credentials('jsmith') - - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end - 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 - @parameters = { - :user => { - :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', - :mail => 'foo@example.net', :password => 'secret123', - :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 => 'secret123' - }}, - {:success_code => :created}) - - should "create a user with the attributes" do - assert_difference('User.count') do - post '/users.xml', @parameters, 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?('secret123') - - 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, 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, 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, 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, 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 - assert_equal '', @response.body - 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, 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 - assert_equal '', @response.body - 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, 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, 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', {}, credentials('admin') - end - - assert_response :ok - assert_equal '', @response.body - 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', {}, credentials('admin') - end - - assert_response :ok - assert_equal '', @response.body - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/c9/c9e75dce917bf0005974fff08b7f395523e3b965.svn-base --- a/.svn/pristine/c9/c9e75dce917bf0005974fff08b7f395523e3b965.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1080 +0,0 @@ -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}." - - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" - - 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_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 - - 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: Emails header - setting_emails_footer: Emails footer - setting_protocol: Protocol - setting_per_page_options: Objects per page options - setting_user_format: Users display format - setting_activity_days_default: Days displayed on project activity - setting_display_subprojects_issues: Display subprojects issues on main projects by default - setting_enabled_scm: Enabled SCM - setting_mail_handler_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 - - 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_manage_documents: Manage documents - permission_view_documents: View documents - permission_manage_files: Manage files - permission_view_files: View files - permission_manage_wiki: Manage wiki - permission_rename_wiki_pages: Rename wiki pages - permission_delete_wiki_pages: Delete wiki pages - permission_view_wiki_pages: View wiki - permission_view_wiki_edits: View wiki history - permission_edit_wiki_pages: Edit wiki pages - permission_delete_wiki_pages_attachments: Delete attachments - permission_protect_wiki_pages: Protect wiki pages - permission_manage_repository: Manage repository - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Commit access - permission_manage_boards: Manage 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_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - 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_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_modification: "%{count} change" - label_modification_plural: "%{count} changes" - 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_author: "Author's %{name}" - label_attribute_of_assigned_to: "Assignee'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 - - 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.
    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. - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c9/c9f7bfe8669185939c28b9d5ebcda0535187eb09.svn-base --- a/.svn/pristine/c9/c9f7bfe8669185939c28b9d5ebcda0535187eb09.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/c9/c9fd7a76ff620549e7000ece93101838a7257ba8.svn-base --- a/.svn/pristine/c9/c9fd7a76ff620549e7000ece93101838a7257ba8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -
    -<% 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca41c33ed3955765c0d4842df7d17bad1f14926d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca41c33ed3955765c0d4842df7d17bad1f14926d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,290 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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_container_for_new_attachment_should_be_nil + assert_nil Attachment.new.container + end + + def test_filename_should_remove_eols + assert_equal "line_feed", Attachment.new(:filename => "line\nfeed").filename + assert_equal "line_feed", Attachment.new(:filename => "some\npath/line\nfeed").filename + assert_equal "carriage_return", Attachment.new(:filename => "carriage\rreturn").filename + assert_equal "carriage_return", Attachment.new(:filename => "some\rpath/carriage\rreturn").filename + 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 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), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + assert !a.save + end + end + + def test_size_should_not_be_validated_when_copying + a = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + with_settings :attachment_max_size => 0 do + copy = a.copy + assert copy.save + end + end + + def test_description_length_should_be_validated + a = Attachment.new(:description => 'a' * 300) + assert !a.save + assert_not_equal [], a.errors[:description] + 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_destroy_should_not_delete_file_referenced_by_other_attachment + a = Attachment.create!(:container => Issue.find(1), + :file => uploaded_test_file("testfile.txt", "text/plain"), + :author => User.find(1)) + diskfile = a.diskfile + + copy = a.copy + copy.save! + + assert File.exists?(diskfile) + a.destroy + assert File.exists?(diskfile) + copy.destroy + assert !File.exists?(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 + + def test_title + a = Attachment.new(:filename => "test.png") + assert_equal "test.png", a.title + + a = Attachment.new(:filename => "test.png", :description => "Cool image") + assert_equal "test.png (Cool image)", a.title + end + + def test_prune_should_destroy_old_unattached_attachments + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago) + Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1) + + assert_difference 'Attachment.count', -2 do + Attachment.prune + end + 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 + + 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 + + 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 + + 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 + + def test_latest_attach + set_fixtures_attachments_directory + 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 + + def test_thumbnailable_should_be_true_for_images + assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable? + end + + def test_thumbnailable_should_be_true_for_non_images + assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable? + end + + if convert_installed? + def test_thumbnail_should_generate_the_thumbnail + set_fixtures_attachments_directory + attachment = Attachment.find(16) + Attachment.clear_thumbnails + + assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do + thumbnail = attachment.thumbnail + assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail) + assert File.exists?(thumbnail) + end + end + else + puts '(ImageMagick convert not available)' + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca427707add26575487d2ee417a7e9710ba1f781.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ca/ca427707add26575487d2ee417a7e9710ba1f781.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca491b24f09d42967bf5ee8640ce6de9cf8a855e.svn-base --- a/.svn/pristine/ca/ca491b24f09d42967bf5ee8640ce6de9cf8a855e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca6a83d87a789a365f28655b1d414ca917f8787b.svn-base --- a/.svn/pristine/ca/ca6a83d87a789a365f28655b1d414ca917f8787b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, :workflows, - :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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca8eec8a84331139968b1ea82955bae2ed0f8643.svn-base --- a/.svn/pristine/ca/ca8eec8a84331139968b1ea82955bae2ed0f8643.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ca/ca9a94f4269bc05500b823a9f42b210c9322360b.svn-base --- a/.svn/pristine/ca/ca9a94f4269bc05500b823a9f42b210c9322360b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +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, :enumerations, - :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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cb/cb011925faaf39d091329859f0fbe2f0e4eeb869.svn-base --- a/.svn/pristine/cb/cb011925faaf39d091329859f0fbe2f0e4eeb869.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cc/cc76092d2df9eadcf4380975fd38796b31db8ed4.svn-base --- a/.svn/pristine/cc/cc76092d2df9eadcf4380975fd38796b31db8ed4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1142 +0,0 @@ -# Vietnamese translation for Ruby on Rails -# by -# Do Hai Bac (dohaibac@gmail.com) -# Dao Thanh Ngoc (ngocdaothanh@gmail.com, http://github.com/ngocdaothanh/rails-i18n/tree/master) -# Nguyen Minh Thien (thiencdcn@gmail.com, http://www.eDesignLab.org) - -vi: - 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: "đồng" - # 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: "30 giây" - less_than_x_seconds: - one: "chưa tới 1 giây" - other: "chưa tới %{count} giây" - x_seconds: - one: "1 giây" - other: "%{count} giây" - less_than_x_minutes: - one: "chưa tới 1 phút" - other: "chưa tới %{count} phút" - x_minutes: - one: "1 phút" - other: "%{count} phút" - about_x_hours: - one: "khoảng 1 giờ" - other: "khoảng %{count} giờ" - x_hours: - one: "1 giờ" - other: "%{count} giờ" - x_days: - one: "1 ngày" - other: "%{count} ngày" - about_x_months: - one: "khoảng 1 tháng" - other: "khoảng %{count} tháng" - x_months: - one: "1 tháng" - other: "%{count} tháng" - about_x_years: - one: "khoảng 1 năm" - other: "khoảng %{count} năm" - over_x_years: - one: "hơn 1 năm" - other: "hơn %{count} năm" - almost_x_years: - one: "gần 1 năm" - other: "gần %{count} năm" - prompts: - year: "Năm" - month: "Tháng" - day: "Ngày" - hour: "Giờ" - minute: "Phút" - second: "Giây" - - activerecord: - errors: - template: - header: - one: "1 lỗi ngăn không cho lưu %{model} này" - other: "%{count} lỗi ngăn không cho lưu %{model} này" - # The variable :count is also available - body: "Có lỗi với các mục sau:" - - # 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: "không có trong danh sách" - exclusion: "đã được giành trước" - invalid: "không hợp lệ" - confirmation: "không khớp với xác nhận" - accepted: "phải được đồng ý" - empty: "không thể rỗng" - blank: "không thể để trắng" - too_long: "quá dài (tối đa %{count} ký tự)" - too_short: "quá ngắn (tối thiểu %{count} ký tự)" - wrong_length: "độ dài không đúng (phải là %{count} ký tự)" - taken: "đã có" - not_a_number: "không phải là số" - greater_than: "phải lớn hơn %{count}" - greater_than_or_equal_to: "phải lớn hơn hoặc bằng %{count}" - equal_to: "phải bằng %{count}" - less_than: "phải nhỏ hơn %{count}" - less_than_or_equal_to: "phải nhỏ hơn hoặc bằng %{count}" - odd: "phải là số chẵn" - even: "phải là số lẻ" - greater_than_start_date: "phải đi sau ngày bắt đầu" - not_same_project: "không thuộc cùng dự án" - circular_dependency: "quan hệ có thể gây ra lặp vô tận" - cant_link_an_issue_with_a_descendant: "Một vấn đề không thể liên kết tới một trong số những tác vụ con của nó" - - 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: ["Chủ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu", "Thứ bảy"] - abbr_day_names: ["Chủ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu", "Thứ bảy"] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, "Tháng một", "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ười một", "Tháng mười hai"] - abbr_month_names: [~, "Tháng một", "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ười một", "Tháng mười hai"] - # Used in date_select and datime_select. - 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áng" - pm: "chiều" - - # Used in array.to_sentence. - support: - array: - words_connector: ", " - two_words_connector: " và " - last_word_connector: ", và " - - actionview_instancetag_blank_option: Vui lòng chọn - - general_text_No: 'Không' - general_text_Yes: 'Có' - general_text_no: 'không' - general_text_yes: 'có' - general_lang_name: 'Tiếng Việt' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Cập nhật tài khoản thành công. - notice_account_invalid_creditentials: Tài khoản hoặc mật mã không hợp lệ - notice_account_password_updated: Cập nhật mật mã thành công. - notice_account_wrong_password: Sai mật mã - notice_account_register_done: Tài khoản được tạo thành công. Để kích hoạt vui lòng làm theo hướng dẫn trong email gửi đến bạn. - notice_account_unknown_email: Không rõ tài khoản. - notice_can_t_change_password: Tài khoản được chứng thực từ nguồn bên ngoài. Không thể đổi mật mã cho loại chứng thực này. - notice_account_lost_email_sent: Thông tin để đổi mật mã mới đã gửi đến bạn qua email. - notice_account_activated: Tài khoản vừa được kích hoạt. Bây giờ bạn có thể đăng nhập. - notice_successful_create: Tạo thành công. - notice_successful_update: Cập nhật thành công. - notice_successful_delete: Xóa thành công. - notice_successful_connection: Kết nối thành công. - notice_file_not_found: Trang bạn cố xem không tồn tại hoặc đã chuyển. - notice_locking_conflict: Thông tin đang được cập nhật bởi người khác. Hãy chép nội dung cập nhật của bạn vào clipboard. - notice_not_authorized: Bạn không có quyền xem trang này. - notice_email_sent: "Email đã được gửi tới %{value}" - notice_email_error: "Lỗi xảy ra khi gửi email (%{value})" - notice_feeds_access_key_reseted: Mã số chứng thực RSS đã được tạo lại. - notice_failed_to_save_issues: "Thất bại khi lưu %{count} vấn đề trong %{total} lựa chọn: %{ids}." - notice_no_issue_selected: "Không có vấn đề được chọn! Vui lòng kiểm tra các vấn đề bạn cần chỉnh sửa." - notice_account_pending: "Thông tin tài khoản đã được tạo ra và đang chờ chứng thực từ ban quản trị." - notice_default_data_loaded: Đã nạp cấu hình mặc định. - notice_unable_delete_version: Không thể xóa phiên bản. - - error_can_t_load_default_data: "Không thể nạp cấu hình mặc định: %{value}" - error_scm_not_found: "Không tìm thấy dữ liệu trong kho chứa." - error_scm_command_failed: "Lỗi xảy ra khi truy cập vào kho lưu trữ: %{value}" - error_scm_annotate: "Đầu vào không tồn tại hoặc không thể chú thích." - error_issue_not_found_in_project: 'Vấn đề không tồn tại hoặc không thuộc dự án' - - mail_subject_lost_password: "%{value}: mật mã của bạn" - mail_body_lost_password: "Để đổi mật mã, hãy click chuột vào liên kết sau:" - mail_subject_register: "%{value}: kích hoạt tài khoản" - mail_body_register: "Để kích hoạt tài khoản, hãy click chuột vào liên kết sau:" - mail_body_account_information_external: " Bạn có thể dùng tài khoản %{value} để đăng nhập." - mail_body_account_information: Thông tin về tài khoản - 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} công việc bạn được phân công sẽ hết hạn trong %{days} ngày tới:" - - 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 đệm và Tên - field_lastname: Họ - field_mail: Email - field_filename: Tập tin - field_filesize: Cỡ - field_downloads: Tải về - field_author: Tác giả - field_created_on: Tạo - field_updated_on: Cập nhật - field_field_format: Định dạng - field_is_for_all: Cho mọi dự án - field_possible_values: Giá trị hợp lệ - field_regexp: Biểu thức chính quy - field_min_length: Chiều dài tối thiểu - field_max_length: Chiều dài tối đa - field_value: Giá trị - field_category: Chủ đề - field_title: Tiêu đề - field_project: Dự án - field_issue: Vấn đề - field_status: Trạng thái - field_notes: Ghi chú - field_is_closed: Vấn đề đóng - field_is_default: Giá trị mặc định - field_tracker: Kiểu vấn đề - field_subject: Chủ đề - field_due_date: Hết hạ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 - field_homepage: Trang chủ - field_is_public: Công cộng - field_parent: Dự án con của - field_is_in_roadmap: Có thể thấy trong Kế hoạch - field_login: Đăng nhập - field_mail_notification: Thông báo qua email - field_admin: Quản trị - field_last_login_on: Kết nối cuối - field_language: Ngôn ngữ - field_effective_date: Ngày - field_password: Mật 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 - field_port: Cổng - field_account: Tài khoản - field_base_dn: Base DN - field_attr_login: Thuộc tính đăng nhập - field_attr_firstname: Thuộc tính tên đệm và Tên - field_attr_lastname: Thuộc tính Họ - field_attr_mail: Thuộc tính Email - field_onthefly: Tạo người dùng tức thì - field_start_date: Bắt đầu - field_done_ratio: Tiến độ - field_auth_source: Chế độ xác thực - 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 - field_subproject: Dự án con - field_hours: Giờ - field_activity: Hoạt động - field_spent_on: Ngày - field_identifier: Mã nhận dạng - field_is_filter: Dùng như 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 lượng - field_column_names: Cột - field_time_zone: Múi giờ - field_searchable: Tìm kiếm được - field_default_value: Giá trị mặc định - field_comments_sorting: Liệt kê bình luận - field_parent_title: Trang mẹ - - setting_app_title: Tựa đề ứng dụng - setting_app_subtitle: Tựa đề nhỏ của ứng dụng - setting_welcome_text: Thông điệp chào mừng - setting_default_language: Ngôn ngữ mặc định - setting_login_required: Cần đăng nhập - setting_self_registration: Tự chứng thực - setting_attachment_max_size: Cỡ tối đa của tập tin đính kèm - setting_issues_export_limit: Giới hạn Export vấn đề - setting_mail_from: Địa chỉ email gửi thông báo - setting_bcc_recipients: Tạo bản CC bí mật (bcc) - setting_host_name: Tên miền và đường dẫn - setting_text_formatting: Định dạng bài viết - setting_wiki_compression: 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à 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 - setting_commit_fix_keywords: Từ khóa chỉ vấn đề đã giải quyết - setting_autologin: Tự động đăng nhập - setting_date_format: Định dạng ngày - setting_time_format: Định dạng giờ - setting_cross_project_issue_relations: Cho phép quan hệ chéo giữa các dự án - setting_issue_list_default_columns: Các cột mặc định hiển thị trong danh sách vấn đề - setting_emails_footer: Chữ ký cuối thư - setting_protocol: Giao thức - setting_per_page_options: Tùy chọn đối tượng mỗi trang - setting_user_format: Định dạng hiển thị người dùng - setting_activity_days_default: Ngày hiển thị hoạt động của dự án - setting_display_subprojects_issues: Hiển thị mặc định vấn đề của dự án con ở dự án chính - setting_enabled_scm: Cho phép SCM - setting_mail_handler_api_enabled: Cho phép WS cho các email tới - setting_mail_handler_api_key: Mã số API - setting_sequential_project_identifiers: Tự sinh chuỗi ID dự án - - project_module_issue_tracking: Theo dõi vấn đề - project_module_time_tracking: Theo dõi thời gian - project_module_news: Tin tức - project_module_documents: Tài liệu - project_module_files: Tập tin - project_module_wiki: Wiki - project_module_repository: Kho lưu trữ - project_module_boards: Diễn đàn - - label_user: Tài khoản - label_user_plural: Tài khoản - label_user_new: Tài khoản mới - label_project: Dự án - label_project_new: Dự án mới - label_project_plural: Dự án - label_x_projects: - zero: không có dự án - one: một dự án - other: "%{count} dự án" - label_project_all: Mọi dự án - label_project_latest: Dự án mới nhất - label_issue: Vấn đề - label_issue_new: Tạo vấn đề mới - label_issue_plural: Vấn đề - label_issue_view_all: Tất cả vấn đề - label_issues_by: "Vấn đề của %{value}" - label_issue_added: Đã thêm vấn đề - label_issue_updated: Vấn đề được cập nhật - label_document: Tài liệu - label_document_new: Tài liệu mới - label_document_plural: Tài liệu - label_document_added: Đã thêm tài liệu - label_role: Vai trò - label_role_plural: Vai trò - label_role_new: Vai trò mới - label_role_and_permissions: Vai trò và Quyền hạn - label_member: Thành viên - label_member_new: Thành viên mới - label_member_plural: Thành viên - label_tracker: 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 đề - label_issue_status_new: Thêm trạng thái - label_issue_category: Chủ đề - label_issue_category_plural: Chủ đề - label_issue_category_new: Chủ đề mới - label_custom_field: Trường tùy biến - label_custom_field_plural: Trường tùy biến - label_custom_field_new: Thêm Trường tùy biến - label_enumerations: Liệt kê - label_enumeration_new: Thêm giá trị - label_information: Thông tin - label_information_plural: Thông tin - label_please_login: Vui lòng đăng nhập - label_register: Đăng ký - label_password_lost: Phục hồi mật mã - label_home: Trang chính - label_my_page: Trang riêng - label_my_account: Cá nhân - label_my_projects: Dự án của bạn - label_administration: Quản trị - label_login: Đăng nhập - label_logout: Thoát - label_help: Giúp đỡ - label_reported_issues: 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 - label_overall_activity: Tất cả hoạt động - label_new: Mới - label_logged_as: Tài khoản » - label_environment: Môi trường - label_authentication: Xác thực - label_auth_source: Chế độ xác thực - label_auth_source_new: Chế độ xác thực mới - label_auth_source_plural: Chế độ xác thực - label_subproject_plural: Dự án con - label_and_its_subprojects: "%{value} và dự án con" - label_min_max_length: Độ dài nhỏ nhất - lớn nhất - label_list: Danh sách - label_date: Ngày - label_integer: Số nguyên - label_float: Số thực - label_boolean: Boolean - label_string: Văn bản - label_text: Văn bản dài - label_attribute: Thuộc tính - label_attribute_plural: Các thuộc tính - label_no_data: Chưa có thông tin gì - label_change_status: Đổi trạng thái - label_history: Lược sử - label_attachment: Tập tin - label_attachment_new: Thêm tập tin mới - label_attachment_delete: Xóa tập tin - label_attachment_plural: Tập tin - label_file_added: Đã thêm tập tin - label_report: Báo cáo - label_report_plural: Báo cáo - label_news: Tin tức - label_news_new: Thêm tin - label_news_plural: Tin tức - label_news_latest: Tin mới - label_news_view_all: Xem mọi tin - label_news_added: Đã thêm tin - label_settings: Thiết lập - label_overview: Tóm tắt - label_version: Phiên bản - label_version_new: Phiên bản mới - label_version_plural: Phiên bản - label_confirmation: Khẳng định - label_export_to: 'Định dạng khác của trang này:' - label_read: Đọc... - label_public_projects: Các dự án công cộng - label_open_issues: mở - label_open_issues_plural: mở - label_closed_issues: đóng - label_closed_issues_plural: đóng - label_x_open_issues_abbr_on_total: - zero: "0 mở / %{total}" - one: "1 mở / %{total}" - other: "%{count} mở / %{total}" - label_x_open_issues_abbr: - zero: 0 mở - one: 1 mở - other: "%{count} mở" - label_x_closed_issues_abbr: - zero: 0 đóng - one: 1 đóng - other: "%{count} đóng" - label_total: Tổng cộng - label_permissions: Quyền - label_current_status: Trạng thái hiện tại - label_new_statuses_allowed: Trạng thái mới được phép - label_all: Tất cả - label_none: không - label_nobody: Chẳng ai - label_next: Sau - label_previous: Trước - label_used_by: Được dùng bởi - label_details: Chi tiết - label_add_note: Thêm ghi chú - label_per_page: Mỗi trang - label_calendar: Lịch - label_months_from: tháng từ - label_gantt: Biểu đồ sự kiện - label_internal: Nội bộ - label_last_changes: "%{count} thay đổi cuối" - label_change_view_all: Xem mọi thay đổi - label_personalize_page: Điều chỉnh trang này - label_comment: Bình luận - label_comment_plural: Bình luận - label_x_comments: - zero: không có bình luận - one: 1 bình luận - other: "%{count} bình luận" - label_comment_add: Thêm bình luận - label_comment_added: Đã thêm bình luận - label_comment_delete: Xóa bình luận - label_query: Truy vấn riêng - label_query_plural: Truy vấn riêng - label_query_new: Truy vấn mới - label_filter_add: Thêm lọc - label_filter_plural: Bộ lọc - label_equals: là - label_not_equals: không là - label_in_less_than: ít hơn - label_in_more_than: nhiều hơn - label_in: trong - label_today: hôm nay - label_all_time: mọi thời gian - label_yesterday: hôm qua - label_this_week: tuần này - label_last_week: tuần trước - label_last_n_days: "%{count} ngày cuối" - label_this_month: tháng này - label_last_month: tháng cuối - label_this_year: năm này - label_date_range: Thời gian - label_less_than_ago: cách đây dưới - label_more_than_ago: cách đây hơn - label_ago: cách đây - label_contains: chứa - label_not_contains: không chứa - label_day_plural: ngày - label_repository: Kho lưu trữ - label_repository_plural: Kho lưu trữ - label_browse: Duyệt - label_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 - label_added: thêm - label_modified: đổi - label_copied: chép - label_renamed: đổi tên - label_deleted: xóa - label_latest_revision: Bản điều chỉnh cuối cùng - label_latest_revision_plural: Bản điều chỉnh cuối cùng - label_view_revisions: Xem các bản điều chỉnh - label_max_size: Dung lượng tối đa - label_sort_highest: Lên trên cùng - label_sort_higher: Dịch lên - label_sort_lower: Dịch xuống - label_sort_lowest: Xuống dưới cùng - label_roadmap: Kế hoạch - label_roadmap_due_in: "Hết hạn trong %{value}" - label_roadmap_overdue: "Trễ %{value}" - label_roadmap_no_issues: Không có vấn đề cho phiên bản này - label_search: Tìm - label_result_plural: Kết quả - label_all_words: Mọi từ - label_wiki: Wiki - label_wiki_edit: Sửa Wiki - label_wiki_edit_plural: Thay đổi wiki - label_wiki_page: Trang wiki - label_wiki_page_plural: Trang wiki - label_index_by_title: Danh sách theo tên - label_index_by_date: Danh sách theo ngày - label_current_version: Bản hiện tại - label_preview: Xem trước - label_feed_plural: Nguồn cấp tin - label_changes_details: Chi tiết của mọi thay đổi - label_issue_tracking: Vấn đề - label_spent_time: Thời gian - label_f_hour: "%{value} giờ" - label_f_hour_plural: "%{value} giờ" - label_time_tracking: Theo dõi thời gian - label_change_plural: Thay đổi - label_statistics: Thống kê - label_commits_per_month: Commits mỗi tháng - label_commits_per_author: Commits mỗi tác giả - label_view_diff: So sánh - label_diff_inline: inline - label_diff_side_by_side: bên cạnh nhau - label_options: Tùy chọn - label_copy_workflow_from: Sao chép quy trình từ - label_permissions_report: Thống kê các quyền - label_watched_issues: Chủ đề đang theo dõi - label_related_issues: Liên quan - label_applied_status: Trạng thái áp dụng - label_loading: Đang xử lý... - label_relation_new: Quan hệ mới - label_relation_delete: Xóa quan hệ - label_relates_to: liên quan - label_duplicates: trùng với - label_duplicated_by: bị trùng bởi - label_blocks: chặn - label_blocked_by: chặn bởi - label_precedes: đi trước - label_follows: đi sau - label_end_to_start: cuối tới đầu - label_end_to_end: cuối tới cuối - label_start_to_start: đầu tớ đầu - label_start_to_end: đầu tới cuối - label_stay_logged_in: Lưu thông tin đăng nhập - label_disabled: Bị vô hiệu - label_show_completed_versions: Xem phiên bản đã hoàn thành - label_me: tôi - label_board: Diễn đàn - label_board_new: Tạo diễn đàn mới - label_board_plural: Diễn đàn - label_topic_plural: Chủ đề - label_message_plural: Diễn đàn - label_message_last: Bài cuối - label_message_new: Tạo bài mới - label_message_posted: Đã thêm bài viết - label_reply_plural: Hồi âm - label_send_information: Gửi thông tin đến người dùng qua email - label_year: Năm - label_month: Tháng - label_week: Tuần - label_date_from: Từ - label_date_to: Đến - label_language_based: Theo ngôn ngữ người dùng - label_sort_by: "Sắp xếp theo %{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: 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 - label_changeset_plural: Thay đổi - label_default_columns: Cột mặc định - label_no_change_option: (không đổi) - label_bulk_edit_selected_issues: Sửa nhiều vấn đề - label_theme: Giao diện - label_default: Mặc định - label_search_titles_only: Chỉ tìm trong tựa đề - label_user_mail_option_all: "Mọi sự kiện trên mọi dự án của 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 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 - label_display_per_page: "mỗi trang: %{value}" - label_age: Thời gian - label_change_properties: Thay đổi thuộc tính - label_general: Tổng quan - label_more: Chi tiết - label_scm: SCM - label_plugins: Module - label_ldap_authentication: Chứng thực LDAP - 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 - label_chronological_order: Bài cũ xếp trước - label_reverse_chronological_order: Bài mới xếp trước - label_planning: Kế hoạch - label_incoming_emails: Nhận mail - label_generate_key: Tạo mã - label_issue_watchers: Theo dõi - - button_login: Đăng nhập - button_submit: Gửi - button_save: Lưu - button_check_all: Đánh dấu tất cả - button_uncheck_all: Bỏ dấu tất cả - button_delete: Xóa - button_create: Tạo - button_test: Kiểm tra - button_edit: Sửa - button_add: Thêm - button_change: Đổi - button_apply: Áp dụng - button_clear: Xóa - button_lock: Khóa - button_unlock: Mở khóa - button_download: Tải về - button_list: Liệt kê - button_view: Xem - button_move: Chuyển - button_back: Quay lại - button_cancel: Bỏ qua - button_activate: Kích hoạt - button_sort: Sắp xếp - button_log_time: Thêm thời gian - button_rollback: Quay trở lại phiên bản này - button_watch: Theo dõi - button_unwatch: Bỏ theo dõi - button_reply: Trả lời - button_archive: Đóng băng - button_unarchive: Xả băng - button_reset: Tạo lại - button_rename: Đổi tên - button_change_password: Đổi mật mã - button_copy: 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: Đ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 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 ? - text_subprojects_destroy_warning: "Dự án con của : %{value} cũng sẽ bị xóa." - text_workflow_edit: Chọn một vai trò và một vấn đề để sửa quy trình - text_are_you_sure: Bạn chắc chứ? - text_tip_issue_begin_day: ngày bắt đầu - text_tip_issue_end_day: ngày kết thúc - text_tip_issue_begin_end_day: bắt đầu và kết thúc cùng ngày - text_caracters_maximum: "Tối đa %{count} ký tự." - text_caracters_minimum: "Phải gồm ít nhất %{count} ký tự." - text_length_between: "Chiều dài giữa %{min} và %{max} ký tự." - text_tracker_no_workflow: Không có quy trình được định nghĩa cho theo dõi này - text_unallowed_characters: Ký tự không hợp lệ - text_comma_separated: Nhiều giá trị được phép (cách nhau bởi dấu phẩy). - text_issues_ref_in_commit_messages: Vấn đề tham khảo và cố định trong ghi chú commit - text_issue_added: "Vấn đề %{id} đã được báo cáo bởi %{author}." - text_issue_updated: "Vấn đề %{id} đã được cập nhật bởi %{author}." - text_wiki_destroy_confirmation: Bạn có chắc chắn muốn xóa trang wiki này và tất cả nội dung của nó ? - text_issue_category_destroy_question: "Một số vấn đề (%{count}) được gán cho danh mục này. Bạn muốn làm gì ?" - text_issue_category_destroy_assignments: Gỡ bỏ danh mục được phân công - text_issue_category_reassign_to: Gán lại vấn đề cho danh mục này - text_user_mail_option: "Với các dự án không được chọn, bạn chỉ có thể nhận được thông báo về các vấn đề bạn đăng ký theo dõi hoặc có liên quan đến bạn (chẳng hạn, vấn đề được gán cho bạn)." - text_no_configuration_data: "Quyền, theo dõi, tình trạng vấn đề và quy trình chưa được cấu hình.\nBắt buộc phải nạp cấu hình mặc định. Bạn sẽ thay đổi nó được sau khi đã nạp." - 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 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 - text_destroy_time_entries_question: "Thời gian %{hours} giờ đã báo cáo trong vấn đề bạn định xóa. Bạn muốn làm gì tiếp ?" - text_destroy_time_entries: Xóa thời gian báo cáo - text_assign_time_entries_to_project: Gán thời gian báo cáo cho dự án - text_reassign_time_entries: 'Gán lại thời gian báo cáo cho Vấn đề này:' - text_user_wrote: "%{value} đã viết:" - text_enumeration_destroy_question: "%{count} đối tượng được gán giá trị này." - 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_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: Đã được giải quyết - default_issue_status_feedback: Phản hồi - default_issue_status_closed: Đã đóng - default_issue_status_rejected: Từ chối - default_doc_category_user: Tài liệu người dùng - default_doc_category_tech: Tài liệu kỹ thuật - default_priority_low: Thấp - default_priority_normal: Bình thường - default_priority_high: Cao - default_priority_urgent: Khẩn cấp - default_priority_immediate: Trung bình - default_activity_design: Thiết kế - default_activity_development: Phát triển - - enumeration_issue_priorities: Mức độ ưu tiên vấn đề - enumeration_doc_categories: 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_gravatar_enabled: Dùng biểu tượng Gravatar - permission_edit_project: Chỉnh dự án - 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ủ đề - permission_add_issues: Thêm vấn đề - permission_edit_issues: Sửa vấn đề - permission_manage_issue_relations: Quản lý quan hệ vấn đề - permission_add_issue_notes: Thêm chú thích - permission_edit_issue_notes: Sửa chú thích - permission_edit_own_issue_notes: Sửa chú thích cá nhân - permission_move_issues: Chuyển vấn đề - permission_delete_issues: Xóa vấn đề - permission_manage_public_queries: Quản lý truy 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 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 đã 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_view_documents: Xem tài liệu - permission_manage_files: Quản lý tập tin - permission_view_files: Xem tập tin - permission_manage_wiki: Quản lý wiki - permission_rename_wiki_pages: Đổi tên trang wiki - permission_delete_wiki_pages: Xóa trang wiki - permission_view_wiki_pages: Xem wiki - permission_view_wiki_edits: Xem lược sử trang wiki - permission_edit_wiki_pages: Sửa trang wiki - permission_delete_wiki_pages_attachments: Xóa tệp đính kèm - permission_protect_wiki_pages: Bảo vệ trang wiki - permission_manage_repository: Quản lý kho lưu trữ - permission_browse_repository: Duyệt kho lưu trữ - permission_view_changesets: Xem các thay đổi - permission_commit_access: Truy cập commit - permission_manage_boards: Quản lý diễn đàn - permission_view_messages: Xem bài viết - permission_add_messages: Gửi bài viết - permission_edit_messages: Sửa bài viết - permission_edit_own_messages: Sửa bài viết cá nhân - permission_delete_messages: Xóa bài viết - permission_delete_own_messages: Xóa bài viết cá nhân - label_example: Ví dụ - text_repository_usernames_mapping: "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}" - text_diff_truncated: '... Thay đổi này đã được cắt bớt do nó vượt qua giới hạn kích thước có thể hiển thị.' - setting_diff_max_lines_displayed: Số dòng thay đổi tối đa được hiển thị - text_plugin_assets_writable: Cho phép ghi thư mục Plugin - warning_attachments_not_saved: "%{count} file không được lưu." - button_create_and_continue: Tạo và tiếp tục - text_custom_field_possible_values_info: 'Một dòng cho mỗi giá trị' - label_display: Hiển thị - field_editable: Có thể sửa được - setting_repository_log_display_limit: Số lượng tối đa các bản điều chỉnh hiển thị trong file log - setting_file_max_size_displayed: Kích thước tối đa của tệp tin văn bản - field_watcher: Người quan sát - setting_openid: Cho phép đăng nhập và đăng ký dùng OpenID - field_identity_url: OpenID URL - label_login_with_open_id_option: hoặc đăng nhập với OpenID - field_content: Nội dung - label_descending: Giảm dần - label_sort: Sắp xếp - label_ascending: Tăng dần - label_date_from_to: "Từ %{start} tới %{end}" - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - text_wiki_page_destroy_question: "Trang này có %{descendants} trang con và trang cháu. Bạn muốn làm gì tiếp?" - text_wiki_page_reassign_children: Gán lại trang con vào trang mẹ này - text_wiki_page_nullify_children: Giữ trang con như trang gốc - text_wiki_page_destroy_children: Xóa trang con và tất cả trang con cháu của nó - setting_password_min_length: Chiều dài tối thiểu của mật khẩu - field_group_by: Nhóm kết quả bởi - mail_subject_wiki_content_updated: "%{id} trang wiki đã được cập nhật" - label_wiki_content_added: Đã thêm trang Wiki - mail_subject_wiki_content_added: "%{id} trang wiki đã được thêm vào" - mail_body_wiki_content_added: "Có %{id} trang wiki đã được thêm vào bởi %{author}." - label_wiki_content_updated: Trang Wiki đã được cập nhật - mail_body_wiki_content_updated: "Có %{id} trang wiki đã được cập nhật bởi %{author}." - permission_add_project: Tạo dự án - setting_new_project_user_role_id: Quyền được gán cho người dùng không phải quản trị viên khi tạo dự án mới - label_view_all_revisions: Xem tất cả bản điều chỉnh - label_tag: Thẻ - label_branch: Nhánh - error_no_tracker_in_project: Không có ai theo dõi dự án này. Hãy kiểm tra lại phần thiết lập cho dự án. - error_no_default_issue_status: Không có vấn đề mặc định được định nghĩa. Vui lòng kiểm tra cấu hình của bạn (Vào "Quản trị -> Trạng thái vấn đề"). - text_journal_changed: "%{label} thay đổi từ %{old} tới %{new}" - text_journal_set_to: "%{label} gán cho %{value}" - text_journal_deleted: "%{label} xóa (%{old})" - label_group_plural: Các nhóm - label_group: Nhóm - label_group_new: Thêm nhóm - label_time_entry_plural: Thời gian đã sử dụng - text_journal_added: "%{label} %{value} được thêm" - field_active: Tích cực - enumeration_system_activity: Hoạt động hệ thống - permission_delete_issue_watchers: Xóa người quan sát - version_status_closed: đóng - version_status_locked: khóa - version_status_open: mở - error_can_not_reopen_issue_on_closed_version: Một vấn đề được gán cho phiên bản đã đóng không thể mở lại được - label_user_anonymous: Ẩn danh - button_move_and_follow: Di chuyển và theo - setting_default_projects_modules: Các Module được kích hoạt mặc định cho dự án mới - setting_gravatar_default: Ảnh Gravatar mặc định - field_sharing: Chia sẻ - label_version_sharing_hierarchy: Với thứ bậc dự án - label_version_sharing_system: Với tất cả dự án - label_version_sharing_descendants: Với dự án con - label_version_sharing_tree: Với cây dự án - label_version_sharing_none: Không chia sẻ - error_can_not_archive_project: Dựa án này không thể lưu trữ được - button_duplicate: Nhân đôi - button_copy_and_follow: Sao chép và theo - label_copy_source: Nguồn - setting_issue_done_ratio: Tính toán tỷ lệ hoàn thành vấn đề với - setting_issue_done_ratio_issue_status: Sử dụng trạng thái của vấn đề - error_issue_done_ratios_not_updated: Tỷ lệ hoàn thành vấn đề không được cập nhật. - error_workflow_copy_target: Vui lòng lựa chọn đích của theo dấu và quyền - setting_issue_done_ratio_issue_field: Dùng trường vấn đề - label_copy_same_as_target: Tương tự như đích - label_copy_target: Đích - notice_issue_done_ratios_updated: Tỷ lệ hoàn thành vấn đề được cập nhật. - error_workflow_copy_source: Vui lòng lựa chọn nguồn của theo dấu hoặc quyền - label_update_issue_done_ratios: Cập nhật tỷ lệ hoàn thành vấn đề - setting_start_of_week: Định dạng lịch - permission_view_issues: Xem Vấn đề - label_display_used_statuses_only: Chỉ hiển thị trạng thái đã được dùng bởi theo dõi này - label_revision_id: "Bản điều chỉnh %{value}" - label_api_access_key: Khoá truy cập API - label_api_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}. Khóa này được dùng cho eDesignLab Client." - label_feeds_access_key: Khoá truy cập RSS - notice_api_access_key_reseted: Khoá truy cập API của bạn đã được đặt lại. - setting_rest_api_enabled: Cho phép dịch vụ web REST - label_missing_api_access_key: Mất Khoá truy cập API - label_missing_feeds_access_key: Mất Khoá truy cập RSS - button_show: Hiện - text_line_separated: Nhiều giá trị được phép(mỗi dòng một giá trị). - setting_mail_handler_body_delimiters: "Cắt bớt email sau những dòng :" - permission_add_subprojects: Tạo Dự án con - label_subproject_new: Thêm dự án con - text_own_membership_delete_confirmation: |- - Bạn đang cố gỡ bỏ một số hoặc tất cả quyền của bạn với dự án này và có thể sẽ mất quyền thay đổi nó sau đó. - Bạn có muốn tiếp tục? - label_close_versions: Đóng phiên bản đã hoàn thành - label_board_sticky: Chú ý - label_board_locked: Đã khóa - permission_export_wiki_pages: Xuất trang wiki - setting_cache_formatted_text: Cache định dạng các ký tự - permission_manage_project_activities: Quản lý hoạt động của dự án - error_unable_delete_issue_status: Không thể xóa trạng thái vấn đề - label_profile: Hồ sơ - permission_manage_subtasks: Quản lý tác vụ con - field_parent_issue: Tác vụ cha - label_subtask_plural: Tác vụ con - label_project_copy_notifications: Gửi email thông báo trong khi dự án được sao chép - error_can_not_delete_custom_field: Không thể xóa trường tùy biến - error_unable_to_connect: "Không thể kết nối (%{value})" - error_can_not_remove_role: Quyền này đang được dùng và không thể xóa được. - error_can_not_delete_tracker: Theo dõi này chứa vấn đề và không thể xóa được. - field_principal: Chủ yếu - label_my_page_block: Block trang của tôi - notice_failed_to_save_members: "Thất bại khi lưu thành viên : %{errors}." - text_zoom_out: Thu nhỏ - text_zoom_in: Phóng to - notice_unable_delete_time_entry: Không thể xóa mục time log. - label_overall_spent_time: Tổng thời gian sử dụng - field_time_entries: Log time - project_module_gantt: Biểu đồ Gantt - project_module_calendar: Lịch - button_edit_associated_wikipage: "Chỉnh sửa trang Wiki liên quan: %{page_title}" - text_are_you_sure_with_children: Xóa vấn đề và tất cả vấn đề con? - field_text: Trường văn bản - label_user_mail_option_only_owner: Chỉ những thứ tôi sở hữu - setting_default_notification_option: Tuỳ chọn thông báo mặc định - label_user_mail_option_only_my_events: Chỉ những thứ tôi theo dõi hoặc liên quan - label_user_mail_option_only_assigned: Chỉ những thứ tôi được phân công - label_user_mail_option_none: Không có sự kiện - field_member_of_group: Nhóm thụ hưởng - field_assigned_to_role: Quyền thụ hưởng - notice_not_authorized_archived_project: Dự án bạn đang có truy cập đã được lưu trữ. - label_principal_search: "Tìm kiếm người dùng hoặc nhóm:" - label_user_search: "Tìm kiếm người dùng:" - field_visible: Nhìn thấy - setting_emails_header: Tiêu đề Email - setting_commit_logtime_activity_id: Cho phép ghi lại thời gian - text_time_logged_by_changeset: "Áp dụng trong changeset : %{value}." - setting_commit_logtime_enabled: Cho phép time logging - notice_gantt_chart_truncated: "Đồ thị đã được cắt bớt bởi vì nó đã vượt qua lượng thông tin tối đa có thể hiển thị :(%{max})" - setting_gantt_items_limit: Lượng thông tin tối đa trên đồ thị gantt - description_selected_columns: Các cột được lựa chọn - field_warn_on_leaving_unsaved: Cảnh báo tôi khi rời một trang có các nội dung chưa lưu - text_warn_on_leaving_unsaved: Trang hiện tại chứa nội dung chưa lưu và sẽ bị mất nếu bạn rời trang này. - label_my_queries: Các truy vấn tùy biến - text_journal_changed_no_detail: "%{label} cập nhật" - label_news_comment_added: Bình luận đã được thêm cho một tin tức - button_expand_all: Mở rộng tất cả - button_collapse_all: Thu gọn tất cả - label_additional_workflow_transitions_for_assignee: Chuyển đổi bổ sung cho phép khi người sử dụng là người nhận chuyển nhượng - label_additional_workflow_transitions_for_author: Các chuyển đổi bổ xung được phép khi người dùng là tác giả - label_bulk_edit_selected_time_entries: Sửa nhiều mục đã chọn - text_time_entries_destroy_confirmation: Bạn có chắc chắn muốn xóa bỏ các mục đã chọn? - label_role_anonymous: Ẩn danh - label_role_non_member: Không là thành viên - label_issue_note_added: Ghi chú được thêm - label_issue_status_updated: Trạng thái cập nhật - label_issue_priority_updated: Cập nhật ưu tiên - label_issues_visibility_own: Vấn đề tạo bởi hoặc gán cho người dùng - field_issues_visibility: Vấn đề được nhìn thấy - label_issues_visibility_all: Tất cả vấn đề - permission_set_own_issues_private: Đặt vấn đề sở hữu là riêng tư hoặc công cộng - field_is_private: Riêng tư - permission_set_issues_private: Gán vấn đề là riêng tư hoặc công cộng - label_issues_visibility_public: Tất cả vấn đề không riêng tư - text_issues_destroy_descendants_confirmation: "Hành động này sẽ xóa %{count} tác vụ con." - field_commit_logs_encoding: Mã hóa ghi chú Commit - field_scm_path_encoding: Mã hóa đường dẫn - text_scm_path_encoding_note: "Mặc định: UTF-8" - field_path_to_repository: Đường dẫn tới kho chứa - field_root_directory: Thư mục gốc - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Kho chứa cục bộ (vd. /hgrepo, c:\hgrepo) - text_scm_command: Lệnh - text_scm_command_version: Phiên bản - label_git_report_last_commit: Báo cáo lần Commit cuối cùng cho file và thư mục - text_scm_config: Bạn có thể cấu hình lệnh Scm trong file config/configuration.yml. Vui lòng khởi động lại ứng dụng sau khi chỉnh sửa nó. - text_scm_command_not_available: Lệnh Scm không có sẵn. Vui lòng kiểm tra lại thiết đặt trong phần Quản trị. - notice_issue_successful_create: "Vấn đề %{id} đã được tạo." - label_between: Ở giữa - setting_issue_group_assignment: Cho phép gán vấn đề đến các nhóm - label_diff: Sự khác nhau - text_git_repository_note: Kho chứa cục bộ và công cộng (vd. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Chiều sắp xếp - description_project_scope: Phạm vi tìm kiếm - description_filter: Lọc - description_user_mail_notification: Thiết lập email thông báo - description_date_from: Nhập ngày bắt đầu - description_message_content: Nội dung thông điệp - description_available_columns: Các cột có sẵn - description_date_range_interval: Chọn khoảng thời gian giữa ngày bắt đầu và kết thúc - description_issue_category_reassign: Chọn danh mục vấn đề - description_search: Trường tìm kiếm - description_notes: Các chú ý - description_date_range_list: Chọn khoảng từ danh sách - description_choose_project: Các dự án - description_date_to: Nhập ngày kết thúc - description_query_sort_criteria_attribute: Sắp xếp thuộc tính - description_wiki_subpages_reassign: Chọn một trang cấp trên - label_parent_revision: Cha - label_child_revision: Con - error_scm_annotate_big_text_file: Các mục không được chú thích, vì nó vượt quá kích thước tập tin văn bản tối đa. - setting_default_issue_start_date_to_creation_date: Sử dụng thời gian hiện tại khi tạo vấn đề mới - button_edit_section: Soạn thảo sự lựa chọn này - setting_repositories_encodings: Mã hóa kho chứa - description_all_columns: Các cột - button_export: Export - label_export_options: "%{export_format} tùy chọn Export" - error_attachment_too_big: "File này không thể tải lên vì nó vượt quá kích thước cho phép : (%{max_size})" - notice_failed_to_save_time_entries: "Lỗi khi lưu %{count} lần trên %{total} sự lựa chọn : %{ids}." - label_x_issues: - zero: 0 vấn đề - one: 1 vấn đề - other: "%{count} vấn đề" - label_repository_new: Kho lưu trữ mới - field_repository_is_default: Kho lưu trữ chính - label_copy_attachments: Copy các file đính kèm - label_item_position: "%{position}/%{count}" - label_completed_versions: Các phiên bản hoàn thành - text_project_identifier_info: Chỉ cho phép chữ cái thường (a-z), con số và dấu gạch ngang.
    Sau khi lưu, chỉ số ID không thể thay đổi. - field_multiple: Nhiều giá trị - setting_commit_cross_project_ref: Sử dụng thời gian hiện tại khi tạo vấn đề mới - text_issue_conflict_resolution_add_notes: Thêm ghi chú của tôi và loại bỏ các thay đổi khác - text_issue_conflict_resolution_overwrite: Áp dụng thay đổi bằng bất cứ giá nào, ghi chú trước đó có thể bị ghi đè - notice_issue_update_conflict: Vấn đề này đã được cập nhật bởi một người dùng khác trong khi bạn đang chỉnh sửa nó. - text_issue_conflict_resolution_cancel: "Loại bỏ tất cả các thay đổi và hiển thị lại %{link}" - permission_manage_related_issues: Quản lý các vấn đề liên quan - field_auth_source_ldap_filter: Bộ lọc LDAP - label_search_for_watchers: Tìm kiếm người theo dõi để thêm - notice_account_deleted: Tài khoản của bạn đã được xóa vĩnh viễn. - button_delete_my_account: Xóa tài khoản của tôi - setting_unsubscribe: Cho phép người dùng xóa Account - text_account_destroy_confirmation: |- - Bạn đồng ý không ? - Tài khoản của bạn sẽ bị xóa vĩnh viễn, không thể khôi phục lại! - error_session_expired: Phiên làm việc của bạn bị quá hạn, hãy đăng nhập lại - text_session_expiration_settings: "Chú ý : Thay đổi các thiết lập này có thể gây vô hiệu hóa Session hiện tại" - setting_session_lifetime: Thời gian tồn tại lớn nhất của Session - setting_session_timeout: Thời gian vô hiệu hóa Session - label_session_expiration: Phiên làm việc bị quá hạn - permission_close_project: Đóng / Mở lại dự án - label_show_closed_projects: Xem các dự án đã đóng - button_close: Đóng - button_reopen: Mở lại - project_status_active: Kích hoạt - project_status_closed: Đã đóng - project_status_archived: Lưu trữ - text_project_closed: Dự án này đã đóng và chỉ đọc - notice_user_successful_create: "Người dùng %{id} đã được tạo." - field_core_fields: Các trường tiêu chuẩn - field_timeout: Quá hạn - setting_thumbnails_enabled: Hiển thị các thumbnail đính kèm - setting_thumbnails_size: Kích thước Thumbnails(pixel) - setting_session_lifetime: Thời gian tồn tại lớn nhất của Session - setting_session_timeout: Thời gian vô hiệu hóa Session - label_status_transitions: Trạng thái chuyển tiếp - label_fields_permissions: Cho phép các trường - label_readonly: Chỉ đọc - label_required: Yêu cầu - text_repository_identifier_info: Chỉ có các chữ thường (a-z), các số (0-9), dấu gạch ngang và gạch dưới là hợp lệ.
    Khi đã lưu, tên định danh sẽ không thể thay đổi. - field_board_parent: Diễn đàn cha - label_attribute_of_project: "Của dự án : %{name}" - label_attribute_of_author: "Của tác giả : %{name}" - label_attribute_of_assigned_to: "Được phân công bởi %{name}" - label_attribute_of_fixed_version: "Phiên bản mục tiêu của %{name}" - label_copy_subtasks: Sao chép các nhiệm vụ con - label_copied_to: Sao chép đến - label_copied_from: Sao chép từ - label_any_issues_in_project: Bất kỳ vấn đề nào trong dự án - label_any_issues_not_in_project: Bất kỳ vấn đề nào không thuộc dự án - field_private_notes: Ghi chú riêng tư - permission_view_private_notes: Xem ghi chú riêng tư - permission_set_notes_private: Đặt ghi chú thành riêng tư - label_no_issues_in_project: Không có vấn đề nào trong dự án - label_any: tất cả - label_last_n_weeks: "%{count} tuần qua" - setting_cross_project_subtasks: Cho phép các nhiệm vụ con liên dự án - label_cross_project_descendants: Trong các dự án con - label_cross_project_tree: Trong cùng cây dự án - label_cross_project_hierarchy: Trong dự án cùng cấp bậc - label_cross_project_system: Trong tất cả các dự án - button_hide: Ẩn - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cc/ccce2abaec3cc91d9ea2ed85e7c358fb0d8c01ed.svn-base --- a/.svn/pristine/cc/ccce2abaec3cc91d9ea2ed85e7c358fb0d8c01ed.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::EnumerationsTest < Redmine::ApiTest::Base - fixtures :enumerations - - def setup - Setting.rest_api_enabled = '1' - end - - context "/enumerations/issue_priorities" do - context "GET" do - - should "return priorities" do - get '/enumerations/issue_priorities.xml' - - assert_response :success - assert_equal 'application/xml', response.content_type - assert_select 'issue_priorities[type=array]' do - assert_select 'issue_priority' do - assert_select 'id', :text => '6' - assert_select 'name', :text => 'High' - end - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cd08316525f41ab12ddac1b7021cfbcc0f617e7e.svn-base --- a/.svn/pristine/cd/cd08316525f41ab12ddac1b7021cfbcc0f617e7e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

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

    - -

    <%= l(:label_history) %>

    - -<%= form_tag({:controller => 'wiki', :action => 'diff', - :project_id => @page.project, :id => @page.title}, - :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}').attr('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 %> - <%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %> -
    -<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %> -<%= pagination_links_full @version_pages, @version_count %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cd3d594ac426506e373d0e5b944a6cc7b681c8b8.svn-base --- a/.svn/pristine/cd/cd3d594ac426506e373d0e5b944a6cc7b681c8b8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cd4379a595a2f49e8f17242a4f6bf12380cbc901.svn-base --- a/.svn/pristine/cd/cd4379a595a2f49e8f17242a4f6bf12380cbc901.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1093 +0,0 @@ -# Indonesian translations -# by Raden Prabowo (cakbowo@gmail.com) - -id: - direction: ltr - date: - formats: - default: "%d-%m-%Y" - short: "%d %b" - long: "%d %B %Y" - - day_names: [Minggu, Senin, Selasa, Rabu, Kamis, Jumat, Sabtu] - abbr_day_names: [Ming, Sen, Sel, Rab, Kam, Jum, Sab] - - month_names: [~, Januari, Februari, Maret, April, Mei, Juni, Juli, Agustus, September, Oktober, November, Desember] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Mei, Jun, Jul, Agu, Sep, Okt, Nov, Des] - order: - - :day - - :month - - :year - - time: - formats: - default: "%a %d %b %Y, %H:%M:%S" - 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: "setengah menit" - less_than_x_seconds: - one: "kurang dari sedetik" - other: "kurang dari %{count} detik" - x_seconds: - one: "sedetik" - other: "%{count} detik" - less_than_x_minutes: - one: "kurang dari semenit" - other: "kurang dari %{count} menit" - x_minutes: - one: "semenit" - other: "%{count} menit" - about_x_hours: - one: "sekitar sejam" - other: "sekitar %{count} jam" - x_hours: - one: "1 jam" - other: "%{count} jam" - x_days: - one: "sehari" - other: "%{count} hari" - about_x_months: - one: "sekitar sebulan" - other: "sekitar %{count} bulan" - x_months: - one: "sebulan" - other: "%{count} bulan" - about_x_years: - one: "sekitar setahun" - other: "sekitar %{count} tahun" - over_x_years: - one: "lebih dari setahun" - other: "lebih dari %{count} tahun" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - precision: 3 - separator: ',' - delimiter: '.' - currency: - format: - unit: 'Rp' - precision: 2 - format: '%n %u' - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: - sentence_connector: "dan" - 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: "tidak termasuk dalam daftar" - exclusion: "sudah dicadangkan" - invalid: "salah" - confirmation: "tidak sesuai konfirmasi" - accepted: "harus disetujui" - empty: "tidak boleh kosong" - blank: "tidak boleh kosong" - too_long: "terlalu panjang (maksimum %{count} karakter)" - too_short: "terlalu pendek (minimum %{count} karakter)" - wrong_length: "panjangnya salah (seharusnya %{count} karakter)" - taken: "sudah diambil" - not_a_number: "bukan angka" - not_a_date: "bukan tanggal" - greater_than: "harus lebih besar dari %{count}" - greater_than_or_equal_to: "harus lebih besar atau sama dengan %{count}" - equal_to: "harus sama dengan %{count}" - less_than: "harus kurang dari %{count}" - less_than_or_equal_to: "harus kurang atau sama dengan %{count}" - odd: "harus ganjil" - even: "harus genap" - greater_than_start_date: "harus lebih besar dari tanggal mulai" - not_same_project: "tidak tergabung dalam proyek yang sama" - circular_dependency: "kaitan ini akan menghasilkan circular dependency" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Silakan pilih - - general_text_No: 'Tidak' - general_text_Yes: 'Ya' - general_text_no: 'tidak' - general_text_yes: 'ya' - general_lang_name: 'Indonesia' - 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: Akun sudah berhasil diperbarui. - notice_account_invalid_creditentials: Pengguna atau kata sandi salah - notice_account_password_updated: Kata sandi sudah berhasil diperbarui. - notice_account_wrong_password: Kata sandi salah. - notice_account_register_done: Akun sudah berhasil dibuat. Untuk mengaktifkan akun anda, silakan klik tautan (link) yang dikirim kepada anda melalui e-mail. - notice_account_unknown_email: Pengguna tidak dikenal. - notice_can_t_change_password: Akun ini menggunakan sumber otentikasi eksternal yang tidak dikenal. Kata sandi tidak bisa diubah. - notice_account_lost_email_sent: Email berisi instruksi untuk memilih kata sandi baru sudah dikirimkan kepada anda. - notice_account_activated: Akun anda sudah diaktifasi. Sekarang anda bisa login. - notice_successful_create: Berhasil dibuat. - notice_successful_update: Berhasil diperbarui. - notice_successful_delete: Berhasil dihapus. - notice_successful_connection: Berhasil terhubung. - notice_file_not_found: Berkas yang anda buka tidak ada atau sudah dihapus. - notice_locking_conflict: Data sudah diubah oleh pengguna lain. - notice_not_authorized: Anda tidak memiliki akses ke halaman ini. - notice_email_sent: "Email sudah dikirim ke %{value}" - notice_email_error: "Terjadi kesalahan pada saat pengiriman email (%{value})" - notice_feeds_access_key_reseted: RSS access key anda sudah direset. - notice_failed_to_save_issues: "Gagal menyimpan %{count} masalah dari %{total} yang dipilih: %{ids}." - notice_no_issue_selected: "Tidak ada masalah yang dipilih! Silakan pilih masalah yang akan anda sunting." - notice_account_pending: "Akun anda sudah dibuat dan sekarang sedang menunggu persetujuan administrator." - notice_default_data_loaded: Konfigurasi default sudah berhasil dimuat. - notice_unable_delete_version: Tidak bisa menghapus versi. - - error_can_t_load_default_data: "Konfigurasi default tidak bisa dimuat: %{value}" - error_scm_not_found: "Entri atau revisi tidak terdapat pada repositori." - error_scm_command_failed: "Terjadi kesalahan pada saat mengakses repositori: %{value}" - error_scm_annotate: "Entri tidak ada, atau tidak dapat di anotasi." - error_issue_not_found_in_project: 'Masalah tidak ada atau tidak tergabung dalam proyek ini.' - error_no_tracker_in_project: 'Tidak ada pelacak yang diasosiasikan pada proyek ini. Silakan pilih Pengaturan Proyek.' - error_no_default_issue_status: 'Nilai default untuk Status masalah belum didefinisikan. Periksa kembali konfigurasi anda (Pilih "Administrasi --> Status masalah").' - error_can_not_reopen_issue_on_closed_version: 'Masalah yang ditujukan pada versi tertutup tidak bisa dibuka kembali' - error_can_not_archive_project: Proyek ini tidak bisa diarsipkan - - warning_attachments_not_saved: "%{count} berkas tidak bisa disimpan." - - mail_subject_lost_password: "Kata sandi %{value} anda" - mail_body_lost_password: 'Untuk mengubah kata sandi anda, klik tautan berikut::' - mail_subject_register: "Aktivasi akun %{value} anda" - mail_body_register: 'Untuk mengaktifkan akun anda, klik tautan berikut:' - mail_body_account_information_external: "Anda dapat menggunakan akun %{value} anda untuk login." - mail_body_account_information: Informasi akun anda - mail_subject_account_activation_request: "Permintaan aktivasi akun %{value} " - mail_body_account_activation_request: "Pengguna baru (%{value}) sudan didaftarkan. Akun tersebut menunggu persetujuan anda:" - mail_subject_reminder: "%{count} masalah harus selesai pada hari berikutnya (%{days})" - mail_body_reminder: "%{count} masalah yang ditugaskan pada anda harus selesai dalam %{days} hari kedepan:" - mail_subject_wiki_content_added: "'%{id}' halaman wiki sudah ditambahkan" - mail_body_wiki_content_added: "The '%{id}' halaman wiki sudah ditambahkan oleh %{author}." - mail_subject_wiki_content_updated: "'%{id}' halaman wiki sudah diperbarui" - mail_body_wiki_content_updated: "The '%{id}' halaman wiki sudah diperbarui oleh %{author}." - - - field_name: Nama - field_description: Deskripsi - field_summary: Ringkasan - field_is_required: Dibutuhkan - field_firstname: Nama depan - field_lastname: Nama belakang - field_mail: Email - field_filename: Berkas - field_filesize: Ukuran - field_downloads: Unduhan - field_author: Pengarang - field_created_on: Dibuat - field_updated_on: Diperbarui - field_field_format: Format - field_is_for_all: Untuk semua proyek - field_possible_values: Nilai yang mungkin - field_regexp: Regular expression - field_min_length: Panjang minimum - field_max_length: Panjang maksimum - field_value: Nilai - field_category: Kategori - field_title: Judul - field_project: Proyek - field_issue: Masalah - field_status: Status - field_notes: Catatan - field_is_closed: Masalah ditutup - field_is_default: Nilai default - field_tracker: Pelacak - field_subject: Perihal - field_due_date: Harus selesai - field_assigned_to: Ditugaskan ke - field_priority: Prioritas - field_fixed_version: Versi target - field_user: Pengguna - field_role: Peran - field_homepage: Halaman web - field_is_public: Publik - field_parent: Subproyek dari - field_is_in_roadmap: Masalah ditampilkan di rencana kerja - field_login: Login - field_mail_notification: Notifikasi email - field_admin: Administrator - field_last_login_on: Terakhir login - field_language: Bahasa - field_effective_date: Tanggal - field_password: Kata sandi - field_new_password: Kata sandi baru - field_password_confirmation: Konfirmasi - field_version: Versi - field_type: Tipe - field_host: Host - field_port: Port - field_account: Akun - field_base_dn: Base DN - field_attr_login: Atribut login - field_attr_firstname: Atribut nama depan - field_attr_lastname: Atribut nama belakang - field_attr_mail: Atribut email - field_onthefly: Pembuatan pengguna seketika - field_start_date: Mulai - field_done_ratio: "% Selesai" - field_auth_source: Mode otentikasi - field_hide_mail: Sembunyikan email saya - field_comments: Komentar - field_url: URL - field_start_page: Halaman awal - field_subproject: Subproyek - field_hours: Jam - field_activity: Kegiatan - field_spent_on: Tanggal - field_identifier: Pengenal - field_is_filter: Digunakan sebagai penyaring - field_issue_to: Masalah terkait - field_delay: Tertunday - field_assignable: Masalah dapat ditugaskan pada peran ini - field_redirect_existing_links: Alihkan tautan yang ada - field_estimated_hours: Perkiraan waktu - field_column_names: Kolom - field_time_zone: Zona waktu - field_searchable: Dapat dicari - field_default_value: Nilai default - field_comments_sorting: Tampilkan komentar - field_parent_title: Halaman induk - field_editable: Dapat disunting - field_watcher: Pemantau - field_identity_url: OpenID URL - field_content: Isi - field_group_by: Dikelompokkan berdasar - field_sharing: Berbagi - - setting_app_title: Judul aplikasi - setting_app_subtitle: Subjudul aplikasi - setting_welcome_text: Teks sambutan - setting_default_language: Bahasa Default - setting_login_required: Butuhkan otentikasi - setting_self_registration: Swa-pendaftaran - setting_attachment_max_size: Ukuran maksimum untuk lampiran - setting_issues_export_limit: Batasan ukuran export masalah - setting_mail_from: Emisi alamat email - setting_bcc_recipients: Blind carbon copy recipients (bcc) - setting_plain_text_mail: Plain text mail (no HTML) - setting_host_name: Nama host dan path - setting_text_formatting: Format teks - setting_wiki_compression: Kompresi untuk riwayat wiki - setting_feeds_limit: Batasan isi feed - setting_default_projects_public: Proyek baru defaultnya adalah publik - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Aktifkan WS untuk pengaturan repositori - setting_commit_ref_keywords: Referensi kaca kunci - setting_commit_fix_keywords: Pembetulan kaca kunci - setting_autologin: Autologin - setting_date_format: Format tanggal - setting_time_format: Format waktu - setting_cross_project_issue_relations: Perbolehkan kaitan masalah proyek berbeda - setting_issue_list_default_columns: Kolom default ditampilkan di daftar masalah - setting_emails_footer: Footer untuk email - setting_protocol: Protokol - setting_per_page_options: Pilihan obyek per halaman - setting_user_format: Format tampilan untuk pengguna - setting_activity_days_default: Hari tertampil pada kegiatan proyek - setting_display_subprojects_issues: Secara default, tampilkan masalah subproyek pada proyek utama - setting_enabled_scm: Enabled SCM - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - setting_sequential_project_identifiers: Buat pengenal proyek terurut - setting_gravatar_enabled: Gunakan icon pengguna dari Gravatar - setting_gravatar_default: Gambar default untuk Gravatar - setting_diff_max_lines_displayed: Maksimum perbedaan baris tertampil - setting_file_max_size_displayed: Maksimum berkas tertampil secara inline - setting_repository_log_display_limit: Nilai maksimum dari revisi ditampilkan di log berkas - setting_openid: Perbolehkan Login dan pendaftaran melalui OpenID - setting_password_min_length: Panjang minimum untuk kata sandi - setting_new_project_user_role_id: Peran diberikan pada pengguna non-admin yang membuat proyek - setting_default_projects_modules: Modul yang diaktifkan pada proyek baru - - permission_add_project: Tambahkan proyek - permission_edit_project: Sunting proyek - permission_select_project_modules: Pilih modul proyek - permission_manage_members: Atur anggota - permission_manage_versions: Atur versi - permission_manage_categories: Atur kategori masalah - permission_add_issues: Tambahkan masalah - permission_edit_issues: Sunting masalah - permission_manage_issue_relations: Atur kaitan masalah - permission_add_issue_notes: Tambahkan catatan - permission_edit_issue_notes: Sunting catatan - permission_edit_own_issue_notes: Sunting catatan saya - permission_move_issues: Pindahkan masalah - permission_delete_issues: Hapus masalah - permission_manage_public_queries: Atur query publik - permission_save_queries: Simpan query - permission_view_gantt: Tampilkan gantt chart - permission_view_calendar: Tampilkan kalender - permission_view_issue_watchers: Tampilkan daftar pemantau - permission_add_issue_watchers: Tambahkan pemantau - permission_delete_issue_watchers: Hapus pemantau - permission_log_time: Log waktu terpakai - permission_view_time_entries: Tampilkan waktu terpakai - permission_edit_time_entries: Sunting catatan waktu - permission_edit_own_time_entries: Sunting catatan waktu saya - permission_manage_news: Atur berita - permission_comment_news: Komentari berita - permission_view_documents: Tampilkan dokumen - permission_manage_files: Atur berkas - permission_view_files: Tampilkan berkas - permission_manage_wiki: Atur wiki - permission_rename_wiki_pages: Ganti nama halaman wiki - permission_delete_wiki_pages: Hapus halaman wiki - permission_view_wiki_pages: Tampilkan wiki - permission_view_wiki_edits: Tampilkan riwayat wiki - permission_edit_wiki_pages: Sunting halaman wiki - permission_delete_wiki_pages_attachments: Hapus lampiran - permission_protect_wiki_pages: Proteksi halaman wiki - permission_manage_repository: Atur repositori - permission_browse_repository: Jelajah repositori - permission_view_changesets: Tampilkan set perubahan - permission_commit_access: Commit akses - permission_manage_boards: Atur forum - permission_view_messages: Tampilkan pesan - permission_add_messages: Tambahkan pesan - permission_edit_messages: Sunting pesan - permission_edit_own_messages: Sunting pesan saya - permission_delete_messages: Hapus pesan - permission_delete_own_messages: Hapus pesan saya - - project_module_issue_tracking: Pelacak masalah - project_module_time_tracking: Pelacak waktu - project_module_news: Berita - project_module_documents: Dokumen - project_module_files: Berkas - project_module_wiki: Wiki - project_module_repository: Repositori - project_module_boards: Forum - - label_user: Pengguna - label_user_plural: Pengguna - label_user_new: Pengguna baru - label_user_anonymous: Anonymous - label_project: Proyek - label_project_new: Proyek baru - label_project_plural: Proyek - label_x_projects: - zero: tidak ada proyek - one: 1 proyek - other: "%{count} proyek" - label_project_all: Semua Proyek - label_project_latest: Proyek terakhir - label_issue: Masalah - label_issue_new: Masalah baru - label_issue_plural: Masalah - label_issue_view_all: tampilkan semua masalah - label_issues_by: "Masalah ditambahkan oleh %{value}" - label_issue_added: Masalah ditambahan - label_issue_updated: Masalah diperbarui - label_document: Dokumen - label_document_new: Dokumen baru - label_document_plural: Dokumen - label_document_added: Dokumen ditambahkan - label_role: Peran - label_role_plural: Peran - label_role_new: Peran baru - label_role_and_permissions: Peran dan perijinan - label_member: Anggota - label_member_new: Anggota baru - label_member_plural: Anggota - label_tracker: Pelacak - label_tracker_plural: Pelacak - label_tracker_new: Pelacak baru - label_workflow: Alur kerja - label_issue_status: Status masalah - label_issue_status_plural: Status masalah - label_issue_status_new: Status baru - label_issue_category: Kategori masalah - label_issue_category_plural: Kategori masalah - label_issue_category_new: Kategori baru - label_custom_field: Field kustom - label_custom_field_plural: Field kustom - label_custom_field_new: Field kustom - label_enumerations: Enumerasi - label_enumeration_new: Buat baru - label_information: Informasi - label_information_plural: Informasi - label_please_login: Silakan login - label_register: mendaftar - label_login_with_open_id_option: atau login menggunakan OpenID - label_password_lost: Lupa password - label_home: Halaman depan - label_my_page: Beranda - label_my_account: Akun saya - label_my_projects: Proyek saya - label_administration: Administrasi - label_login: Login - label_logout: Keluar - label_help: Bantuan - label_reported_issues: Masalah terlapor - label_assigned_to_me_issues: Masalah yang ditugaskan pada saya - label_last_login: Terakhir login - label_registered_on: Terdaftar pada - label_activity: Kegiatan - label_overall_activity: Kegiatan umum - label_user_activity: "kegiatan %{value}" - label_new: Baru - label_logged_as: Login sebagai - label_environment: Lingkungan - label_authentication: Otentikasi - label_auth_source: Mode Otentikasi - label_auth_source_new: Mode otentikasi baru - label_auth_source_plural: Mode Otentikasi - label_subproject_plural: Subproyek - label_and_its_subprojects: "%{value} dan subproyeknya" - label_min_max_length: Panjang Min - Maks - label_list: Daftar - label_date: Tanggal - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Atribut - label_attribute_plural: Atribut - label_no_data: Tidak ada data untuk ditampilkan - label_change_status: Status perubahan - label_history: Riwayat - label_attachment: Berkas - label_attachment_new: Berkas baru - label_attachment_delete: Hapus Berkas - label_attachment_plural: Berkas - label_file_added: Berkas ditambahkan - label_report: Laporan - label_report_plural: Laporan - label_news: Berita - label_news_new: Tambahkan berita - label_news_plural: Berita - label_news_latest: Berita terakhir - label_news_view_all: Tampilkan semua berita - label_news_added: Berita ditambahkan - label_settings: Pengaturan - label_overview: Umum - label_version: Versi - label_version_new: Versi baru - label_version_plural: Versi - label_confirmation: Konfirmasi - label_export_to: 'Juga tersedia dalam:' - label_read: Baca... - label_public_projects: Proyek publik - label_open_issues: belum selesai - label_open_issues_plural: belum selesai - label_closed_issues: selesai - label_closed_issues_plural: selesai - label_x_open_issues_abbr_on_total: - zero: 0 belum selesai / %{total} - one: 1 belum selesai / %{total} - other: "%{count} terbuka / %{total}" - label_x_open_issues_abbr: - zero: 0 belum selesai - one: 1 belum selesai - other: "%{count} belum selesai" - label_x_closed_issues_abbr: - zero: 0 selesai - one: 1 selesai - other: "%{count} selesai" - label_total: Total - label_permissions: Perijinan - label_current_status: Status sekarang - label_new_statuses_allowed: Status baru yang diijinkan - label_all: semua - label_none: tidak ada - label_nobody: tidak ada - label_next: Berikut - label_previous: Sebelum - label_used_by: Digunakan oleh - label_details: Rincian - label_add_note: Tambahkan catatan - label_per_page: Per halaman - label_calendar: Kalender - label_months_from: dari bulan - label_gantt: Gantt - label_internal: Internal - label_last_changes: "%{count} perubahan terakhir" - label_change_view_all: Tampilkan semua perubahan - label_personalize_page: Personalkan halaman ini - label_comment: Komentar - label_comment_plural: Komentar - label_x_comments: - zero: tak ada komentar - one: 1 komentar - other: "%{count} komentar" - label_comment_add: Tambahkan komentar - label_comment_added: Komentar ditambahkan - label_comment_delete: Hapus komentar - label_query: Custom query - label_query_plural: Custom queries - label_query_new: Query baru - label_filter_add: Tambahkan filter - label_filter_plural: Filter - label_equals: sama dengan - label_not_equals: tidak sama dengan - label_in_less_than: kurang dari - label_in_more_than: lebih dari - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: pada - label_today: hari ini - label_all_time: semua waktu - label_yesterday: kemarin - label_this_week: minggu ini - label_last_week: minggu lalu - label_last_n_days: "%{count} hari terakhir" - label_this_month: bulan ini - label_last_month: bulan lalu - label_this_year: this year - label_date_range: Jangkauan tanggal - label_less_than_ago: kurang dari hari yang lalu - label_more_than_ago: lebih dari hari yang lalu - label_ago: hari yang lalu - label_contains: berisi - label_not_contains: tidak berisi - label_day_plural: hari - label_repository: Repositori - label_repository_plural: Repositori - label_browse: Jelajah - label_branch: Cabang - label_tag: Tag - label_revision: Revisi - label_revision_plural: Revisi - label_associated_revisions: Revisi terkait - label_added: ditambahkan - label_modified: diubah - label_copied: disalin - label_renamed: diganti nama - label_deleted: dihapus - label_latest_revision: Revisi terakhir - label_latest_revision_plural: Revisi terakhir - label_view_revisions: Tampilkan revisi - label_view_all_revisions: Tampilkan semua revisi - label_max_size: Ukuran maksimum - label_sort_highest: Ke paling atas - label_sort_higher: Ke atas - label_sort_lower: Ke bawah - label_sort_lowest: Ke paling bawah - label_roadmap: Rencana kerja - label_roadmap_due_in: "Harus selesai dalam %{value}" - label_roadmap_overdue: "%{value} terlambat" - label_roadmap_no_issues: Tak ada masalah pada versi ini - label_search: Cari - label_result_plural: Hasil - label_all_words: Semua kata - label_wiki: Wiki - label_wiki_edit: Sunting wiki - label_wiki_edit_plural: Sunting wiki - label_wiki_page: Halaman wiki - label_wiki_page_plural: Halaman wiki - label_index_by_title: Indeks menurut judul - label_index_by_date: Indeks menurut tanggal - label_current_version: Versi sekarang - label_preview: Tinjauan - label_feed_plural: Feeds - label_changes_details: Rincian semua perubahan - label_issue_tracking: Pelacak masalah - label_spent_time: Waktu terpakai - label_f_hour: "%{value} jam" - label_f_hour_plural: "%{value} jam" - label_time_tracking: Pelacak waktu - label_change_plural: Perubahan - label_statistics: Statistik - label_commits_per_month: Komit per bulan - label_commits_per_author: Komit per pengarang - label_view_diff: Tampilkan perbedaan - label_diff_inline: inline - label_diff_side_by_side: berdampingan - label_options: Pilihan - label_copy_workflow_from: Salin alur kerja dari - label_permissions_report: Laporan perijinan - label_watched_issues: Masalah terpantau - label_related_issues: Masalah terkait - label_applied_status: Status teraplikasi - label_loading: Memuat... - label_relation_new: Kaitan baru - label_relation_delete: Hapus kaitan - label_relates_to: terkait pada - label_duplicates: salinan - label_duplicated_by: disalin oleh - label_blocks: blok - label_blocked_by: diblok oleh - label_precedes: mendahului - label_follows: mengikuti - label_end_to_start: akhir ke awal - label_end_to_end: akhir ke akhir - label_start_to_start: awal ke awal - label_start_to_end: awal ke akhir - label_stay_logged_in: Tetap login - label_disabled: tidak diaktifkan - label_show_completed_versions: Tampilkan versi lengkap - label_me: saya - label_board: Forum - label_board_new: Forum baru - label_board_plural: Forum - label_topic_plural: Topik - label_message_plural: Pesan - label_message_last: Pesan terakhir - label_message_new: Pesan baru - label_message_posted: Pesan ditambahkan - label_reply_plural: Balasan - label_send_information: Kirim informasi akun ke pengguna - label_year: Tahun - label_month: Bulan - label_week: Minggu - label_date_from: Dari - label_date_to: Sampai - label_language_based: Berdasarkan bahasa pengguna - label_sort_by: "Urut berdasarkan %{value}" - label_send_test_email: Kirim email percobaan - label_feeds_access_key_created_on: "RSS access key dibuat %{value} yang lalu" - label_module_plural: Modul - label_added_time_by: "Ditambahkan oleh %{author} %{age} yang lalu" - label_updated_time_by: "Diperbarui oleh %{author} %{age} yang lalu" - label_updated_time: "Diperbarui oleh %{value} yang lalu" - label_jump_to_a_project: Pilih proyek... - label_file_plural: Berkas - label_changeset_plural: Set perubahan - label_default_columns: Kolom default - label_no_change_option: (Tak ada perubahan) - label_bulk_edit_selected_issues: Ubah masalah terpilih secara masal - label_theme: Tema - label_default: Default - label_search_titles_only: Cari judul saja - label_user_mail_option_all: "Untuk semua kejadian pada semua proyek saya" - label_user_mail_option_selected: "Hanya untuk semua kejadian pada proyek yang saya pilih ..." - label_user_mail_no_self_notified: "Saya tak ingin diberitahu untuk perubahan yang saya buat sendiri" - label_user_mail_assigned_only_mail_notification: "Kirim email hanya bila saya ditugaskan untuk masalah terkait" - label_user_mail_block_mail_notification: "Saya tidak ingin menerima email. Terima kasih." - label_registration_activation_by_email: aktivasi akun melalui email - label_registration_manual_activation: aktivasi akun secara manual - label_registration_automatic_activation: aktivasi akun secara otomatis - label_display_per_page: "Per halaman: %{value}" - label_age: Umur - label_change_properties: Rincian perubahan - label_general: Umum - label_more: Lanjut - label_scm: SCM - label_plugins: Plugin - label_ldap_authentication: Otentikasi LDAP - label_downloads_abbr: Unduh - label_optional_description: Deskripsi optional - label_add_another_file: Tambahkan berkas lain - label_preferences: Preferensi - label_chronological_order: Urut sesuai kronologis - label_reverse_chronological_order: Urut dari yang terbaru - label_planning: Perencanaan - label_incoming_emails: Email masuk - label_generate_key: Buat kunci - label_issue_watchers: Pemantau - label_example: Contoh - label_display: Tampilan - label_sort: Urut - label_ascending: Menaik - label_descending: Menurun - label_date_from_to: Dari %{start} sampai %{end} - label_wiki_content_added: Halaman wiki ditambahkan - label_wiki_content_updated: Halaman wiki diperbarui - label_group: Kelompok - label_group_plural: Kelompok - label_group_new: Kelompok baru - label_time_entry_plural: Waktu terpakai - label_version_sharing_none: Tidak dibagi - label_version_sharing_descendants: Dengan subproyek - label_version_sharing_hierarchy: Dengan hirarki proyek - label_version_sharing_tree: Dengan pohon proyek - label_version_sharing_system: Dengan semua proyek - - - button_login: Login - button_submit: Kirim - button_save: Simpan - button_check_all: Contreng semua - button_uncheck_all: Hilangkan semua contreng - button_delete: Hapus - button_create: Buat - button_create_and_continue: Buat dan lanjutkan - button_test: Test - button_edit: Sunting - button_add: Tambahkan - button_change: Ubah - button_apply: Terapkan - button_clear: Bersihkan - button_lock: Kunci - button_unlock: Buka kunci - button_download: Unduh - button_list: Daftar - button_view: Tampilkan - button_move: Pindah - button_move_and_follow: Pindah dan ikuti - button_back: Kembali - button_cancel: Batal - button_activate: Aktifkan - button_sort: Urut - button_log_time: Rekam waktu - button_rollback: Kembali ke versi ini - button_watch: Pantau - button_unwatch: Tidak Memantau - button_reply: Balas - button_archive: Arsip - button_unarchive: Batalkan arsip - button_reset: Reset - button_rename: Ganti nama - button_change_password: Ubah kata sandi - button_copy: Salin - button_copy_and_follow: Salin dan ikuti - button_annotate: Anotasi - button_update: Perbarui - button_configure: Konfigur - button_quote: Kutip - button_duplicate: Duplikat - - status_active: aktif - status_registered: terdaftar - status_locked: terkunci - - version_status_open: terbuka - version_status_locked: terkunci - version_status_closed: tertutup - - field_active: Aktif - - text_select_mail_notifications: Pilih aksi dimana email notifikasi akan dikirimkan. - text_regexp_info: mis. ^[A-Z0-9]+$ - text_min_max_length_info: 0 berarti tidak ada pembatasan - text_project_destroy_confirmation: Apakah anda benar-benar akan menghapus proyek ini beserta data terkait ? - text_subprojects_destroy_warning: "Subproyek: %{value} juga akan dihapus." - text_workflow_edit: Pilih peran dan pelacak untuk menyunting alur kerja - text_are_you_sure: Anda yakin ? - text_journal_changed: "%{label} berubah dari %{old} menjadi %{new}" - text_journal_set_to: "%{label} di set ke %{value}" - text_journal_deleted: "%{label} dihapus (%{old})" - text_journal_added: "%{label} %{value} ditambahkan" - text_tip_issue_begin_day: tugas dimulai hari itu - text_tip_issue_end_day: tugas berakhir hari itu - text_tip_issue_begin_end_day: tugas dimulai dan berakhir hari itu - text_caracters_maximum: "maximum %{count} karakter." - text_caracters_minimum: "Setidaknya harus sepanjang %{count} karakter." - text_length_between: "Panjang diantara %{min} dan %{max} karakter." - text_tracker_no_workflow: Tidak ada alur kerja untuk pelacak ini - text_unallowed_characters: Karakter tidak diperbolehkan - text_comma_separated: Beberapa nilai diperbolehkan (dipisahkan koma). - text_issues_ref_in_commit_messages: Mereferensikan dan membetulkan masalah pada pesan komit - text_issue_added: "Masalah %{id} sudah dilaporkan oleh %{author}." - text_issue_updated: "Masalah %{id} sudah diperbarui oleh %{author}." - text_wiki_destroy_confirmation: Apakah anda benar-benar akan menghapus wiki ini beserta semua isinya ? - text_issue_category_destroy_question: "Beberapa masalah (%{count}) ditugaskan pada kategori ini. Apa yang anda lakukan ?" - text_issue_category_destroy_assignments: Hapus kategori penugasan - text_issue_category_reassign_to: Tugaskan kembali masalah untuk kategori ini - text_user_mail_option: "Untuk proyek yang tidak dipilih, anda hanya akan menerima notifikasi hal-hal yang anda pantau atau anda terlibat di dalamnya (misalnya masalah yang anda tulis atau ditugaskan pada anda)." - text_no_configuration_data: "Peran, pelacak, status masalah dan alur kerja belum dikonfigur.\nSangat disarankan untuk memuat konfigurasi default. Anda akan bisa mengubahnya setelah konfigurasi dimuat." - text_load_default_configuration: Muat konfigurasi default - text_status_changed_by_changeset: "Diterapkan di set perubahan %{value}." - text_issues_destroy_confirmation: 'Apakah anda yakin untuk menghapus masalah terpilih ?' - text_select_project_modules: 'Pilih modul untuk diaktifkan pada proyek ini:' - text_default_administrator_account_changed: Akun administrator default sudah berubah - text_file_repository_writable: Direktori yang bisa ditulisi untuk lampiran - text_plugin_assets_writable: Direktori yang bisa ditulisi untuk plugin asset - text_rmagick_available: RMagick tersedia (optional) - text_destroy_time_entries_question: "%{hours} jam sudah dilaporkan pada masalah yang akan anda hapus. Apa yang akan anda lakukan ?" - text_destroy_time_entries: Hapus jam yang terlapor - text_assign_time_entries_to_project: Tugaskan jam terlapor pada proyek - text_reassign_time_entries: 'Tugaskan kembali jam terlapor pada masalah ini:' - text_user_wrote: "%{value} menulis:" - text_enumeration_destroy_question: "%{count} obyek ditugaskan untuk nilai ini." - text_enumeration_category_reassign_to: 'Tugaskan kembali untuk nilai ini:' - text_email_delivery_not_configured: "Pengiriman email belum dikonfigurasi, notifikasi tidak diaktifkan.\nAnda harus mengkonfigur SMTP server anda pada config/configuration.yml dan restart kembali aplikasi untuk mengaktifkan." - text_repository_usernames_mapping: "Pilih atau perbarui pengguna Redmine yang terpetakan ke setiap nama pengguna yang ditemukan di log repositori.\nPengguna dengan nama pengguna dan repositori atau email yang sama secara otomasit akan dipetakan." - text_diff_truncated: '... Perbedaan terpotong karena melebihi batas maksimum yang bisa ditampilkan.' - text_custom_field_possible_values_info: 'Satu baris untuk setiap nilai' - text_wiki_page_destroy_question: "Halaman ini mempunyai %{descendants} halaman anak dan turunannya. Apa yang akan anda lakukan ?" - text_wiki_page_nullify_children: "Biarkan halaman anak sebagai halaman teratas (root)" - text_wiki_page_destroy_children: "Hapus halaman anak dan semua turunannya" - text_wiki_page_reassign_children: "Tujukan halaman anak ke halaman induk yang ini" - - default_role_manager: Manager - default_role_developer: Pengembang - default_role_reporter: Pelapor - default_tracker_bug: Bug - default_tracker_feature: Fitur - default_tracker_support: Dukungan - default_issue_status_new: Baru - default_issue_status_in_progress: Dalam proses - default_issue_status_resolved: Resolved - default_issue_status_feedback: Umpan balik - default_issue_status_closed: Ditutup - default_issue_status_rejected: Ditolak - default_doc_category_user: Dokumentasi pengguna - default_doc_category_tech: Dokumentasi teknis - default_priority_low: Rendah - default_priority_normal: Normal - default_priority_high: Tinggi - default_priority_urgent: Penting - default_priority_immediate: Segera - default_activity_design: Rancangan - default_activity_development: Pengembangan - - enumeration_issue_priorities: Prioritas masalah - enumeration_doc_categories: Kategori dokumen - enumeration_activities: Kegiatan - enumeration_system_activity: Kegiatan Sistem - label_copy_source: Source - label_update_issue_done_ratios: Update issue done ratios - setting_issue_done_ratio: Calculate the issue done ratio with - label_api_access_key: API access key - text_line_separated: Multiple values allowed (one line for each value). - label_revision_id: Revision %{value} - permission_view_issues: View Issues - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - label_display_used_statuses_only: Only display statuses that are used by this tracker - error_workflow_copy_target: Please select target tracker(s) and role(s) - 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_copy_same_as_target: Same as target - button_show: Show - setting_issue_done_ratio_issue_field: Use the issue field - label_missing_api_access_key: Missing an API access key - label_copy_target: Target - label_missing_feeds_access_key: Missing a RSS access key - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - setting_start_of_week: Start calendars on - 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: 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 masalah - one: 1 masalah - other: "%{count} masalah" - 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: semua - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Dengan subproyek - label_cross_project_tree: Dengan pohon proyek - label_cross_project_hierarchy: Dengan hirarki proyek - label_cross_project_system: Dengan semua proyek - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cd49a55900aad4b25d728efce849eefc8dc672da.svn-base --- a/.svn/pristine/cd/cd49a55900aad4b25d728efce849eefc8dc672da.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -
      - <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %> - - <% if @issue -%> -
    • <%= context_menu_link l(:button_edit), edit_issue_path(@issue), - :class => 'icon-edit', :disabled => !@can[:edit] %>
    • - <% else %> -
    • <%= context_menu_link l(:button_edit), bulk_edit_issues_path(:ids => @issue_ids), - :class => 'icon-edit', :disabled => !@can[:edit] %>
    • - <% end %> - - <% if @allowed_statuses.present? %> -
    • - <%= l(:field_status) %> -
        - <% @allowed_statuses.each do |s| -%> -
      • <%= context_menu_link h(s.name), bulk_update_issues_path(:ids => @issue_ids, :issue => {:status_id => s}, :back_url => @back), :method => :post, - :selected => (@issue && s == @issue.status), :disabled => !@can[:update] %>
      • - <% end -%> -
      -
    • - <% end %> - - <% if @trackers.present? %> -
    • - <%= l(:field_tracker) %> -
        - <% @trackers.each do |t| -%> -
      • <%= context_menu_link h(t.name), bulk_update_issues_path(:ids => @issue_ids, :issue => {'tracker_id' => t}, :back_url => @back), :method => :post, - :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %>
      • - <% end -%> -
      -
    • - <% end %> - - <% if @safe_attributes.include?('priority_id') && @priorities.present? -%> -
    • - <%= l(:field_priority) %> -
        - <% @priorities.each do |p| -%> -
      • <%= context_menu_link h(p.name), bulk_update_issues_path(:ids => @issue_ids, :issue => {'priority_id' => p}, :back_url => @back), :method => :post, - :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %>
      • - <% end -%> -
      -
    • - <% end %> - - <% if @safe_attributes.include?('fixed_version_id') && @versions.present? -%> -
    • - <%= l(:field_fixed_version) %> -
        - <% @versions.sort.each do |v| -%> -
      • <%= context_menu_link format_version_name(v), bulk_update_issues_path(:ids => @issue_ids, :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), bulk_update_issues_path(:ids => @issue_ids, :issue => {'fixed_version_id' => 'none'}, :back_url => @back), :method => :post, - :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %>
      • -
      -
    • - <% end %> - - <% if @safe_attributes.include?('assigned_to_id') && @assignables.present? -%> -
    • - <%= l(:field_assigned_to) %> -
        - <% if @assignables.include?(User.current) %> -
      • <%= context_menu_link "<< #{l(:label_me)} >>", bulk_update_issues_path(:ids => @issue_ids, :issue => {'assigned_to_id' => User.current}, :back_url => @back), :method => :post, - :disabled => !@can[:update] %>
      • - <% end %> - <% @assignables.each do |u| -%> -
      • <%= context_menu_link h(u.name), bulk_update_issues_path(:ids => @issue_ids, :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), bulk_update_issues_path(:ids => @issue_ids, :issue => {'assigned_to_id' => 'none'}, :back_url => @back), :method => :post, - :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %>
      • -
      -
    • - <% end %> - - <% if @safe_attributes.include?('category_id') && @project && @project.issue_categories.any? -%> -
    • - <%= l(:field_category) %> -
        - <% @project.issue_categories.each do |u| -%> -
      • <%= context_menu_link h(u.name), bulk_update_issues_path(:ids => @issue_ids, :issue => {'category_id' => u}, :back_url => @back), :method => :post, - :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %>
      • - <% end -%> -
      • <%= context_menu_link l(:label_none), bulk_update_issues_path(:ids => @issue_ids, :issue => {'category_id' => 'none'}, :back_url => @back), :method => :post, - :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %>
      • -
      -
    • - <% end -%> - - <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %> -
    • - <%= l(:field_done_ratio) %> -
        - <% (0..10).map{|x|x*10}.each do |p| -%> -
      • <%= context_menu_link "#{p}%", bulk_update_issues_path(:ids => @issue_ids, :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 %> - - <% @options_by_custom_field.each do |field, options| %> -
    • - <%= h(field.name) %> -
        - <% options.each do |text, value| %> -
      • <%= bulk_update_custom_field_context_menu_link(field, text, value || text) %>
      • - <% end %> - <% unless field.is_required? %> -
      • <%= bulk_update_custom_field_context_menu_link(field, l(:label_none), '__none__') %>
      • - <% end %> -
      -
    • - <% end %> - -<% if User.current.logged? %> -
    • <%= watcher_link(@issues, User.current) %>
    • -<% end %> - -<% if @issue.present? %> - <% if @can[:log_time] -%> -
    • <%= context_menu_link l(:button_log_time), new_issue_time_entry_path(@issue), - :class => 'icon-time-add' %>
    • - <% end %> -
    • <%= context_menu_link l(:button_copy), project_copy_issue_path(@project, @issue), - :class => 'icon-copy', :disabled => !@can[:copy] %>
    • -<% else %> -
    • <%= context_menu_link l(:button_copy), bulk_edit_issues_path(:ids => @issue_ids, :copy => '1'), - :class => 'icon-copy', :disabled => !@can[:move] %>
    • -<% end %> -
    • <%= context_menu_link l(:button_delete), issues_path(:ids => @issue_ids, :back_url => @back), - :method => :delete, :data => {: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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cd616cb8a21cd26998b00199d18b19fd0b8aadef.svn-base --- a/.svn/pristine/cd/cd616cb8a21cd26998b00199d18b19fd0b8aadef.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -
    -<% if User.current.allowed_to?(:manage_documents, @project) %> -<%= link_to l(:button_edit), edit_document_path(@document), :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> -<%= 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cdbe648d6f9e337038e30cb5fa01921b70b5cc71.svn-base --- a/.svn/pristine/cd/cdbe648d6f9e337038e30cb5fa01921b70b5cc71.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1090 +0,0 @@ -mn: - direction: ltr - jquery: - locale: "en" - 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_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 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}." - - - 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_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_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: 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_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}" - 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: 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: Нийт - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cd/cdd5355f3247a131962bb1b3e4aef9c1b10eacdb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cd/cdd5355f3247a131962bb1b3e4aef9c1b10eacdb.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,71 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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, :add_role_to_subprojects + after_destroy :remove_inherited_roles + + validates_presence_of :role + validate :validate_role_member + + def validate_role_member + 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) && !inherited? + member.principal.users.each do |user| + 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 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/ce029d1276c9c02edcd3a28e6f067a6b863bd8d9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce029d1276c9c02edcd3a28e6f067a6b863bd8d9.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,27 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/ce1d1b7dae18c3f2642bcd49171987cc00fe9c59.svn-base --- a/.svn/pristine/ce/ce1d1b7dae18c3f2642bcd49171987cc00fe9c59.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -source 'http://rubygems.org' - -gem "rails", "3.2.13" -gem "jquery-rails", "~> 2.0.2" -gem "i18n", "~> 0.6.0" -gem "coderay", "~> 1.0.6" -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 - -# Database gems -platforms :mri, :mingw do - group :postgresql do - gem "pg", ">= 0.11.0" - end - - group :sqlite do - gem "sqlite3" - end -end - -platforms :mri_18, :mingw_18 do - group :mysql do - gem "mysql", "~> 2.8.1" - 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 -end - -group :development do - gem "rdoc", ">= 2.4.2" - gem "yard" -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 "mocha", "~> 0.13.3" -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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/ce296b72f23f19005cbacbf0993e6db53bb2b243.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce296b72f23f19005cbacbf0993e6db53bb2b243.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,128 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/ce353587e94a2173fc11172d92c51303bcbfa3ef.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/ce353587e94a2173fc11172d92c51303bcbfa3ef.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,166 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/cea51a8cc1d29add5095e952d8b4a92de6169347.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/cea51a8cc1d29add5095e952d8b4a92de6169347.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,242 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/ceab695f77fe3ebbfee36cd481f3a4c77d758f4f.svn-base --- a/.svn/pristine/ce/ceab695f77fe3ebbfee36cd481f3a4c77d758f4f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -<%= 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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/cebffaae753a7cc849c7601935577fcde31c4695.svn-base --- a/.svn/pristine/ce/cebffaae753a7cc849c7601935577fcde31c4695.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +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| %> -

    <%= link_to_version version, :name => version_anchor(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_anchor(version)}" %>
    -<% end %> -<% if @completed_versions.present? %> -

    - <%= link_to_function l(:label_completed_versions), - '$("#toggle-completed-versions").toggleClass("collapsed"); $("#completed-versions").toggle()', - :id => 'toggle-completed-versions', :class => 'collapsible collapsed' %>
    - -

    -<% end %> -<% end %> - -<% html_title(l(:label_roadmap)) %> - -<%= context_menu issues_context_menu_path %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ce/cef18514271092169d5d166f4163b28499491884.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ce/cef18514271092169d5d166f4163b28499491884.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,178 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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.deliver_issue_add(issue) + mail = ActionMailer::Base.deliveries.last + + @hook_module.add_listener(TestLinkToHook) + hook_helper.call_hook(:view_layouts_base_html_head) + + ActionMailer::Base.deliveries.clear + Mailer.deliver_issue_add(issue) + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf0bca266fbc21a03db30e949b23e76b71b26da6.svn-base --- a/.svn/pristine/cf/cf0bca266fbc21a03db30e949b23e76b71b26da6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::TokenAuthenticationTest < 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 = '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_key_based_auth(:get, "/news.xml") - end - - context "in :json format" do - should_allow_key_based_auth(:get, "/news.json") - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf164880642b001e32b75655aabc9764c7865713.svn-base --- a/.svn/pristine/cf/cf164880642b001e32b75655aabc9764c7865713.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf1f7ec64f7ca44a3fcc2b553d180407341c51aa.svn-base --- a/.svn/pristine/cf/cf1f7ec64f7ca44a3fcc2b553d180407341c51aa.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 %> <%= h(' - ') + 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}, - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> - <% end -%> -
    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf33cf57a3e921509ff6e96342de19ecb5f2697c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cf/cf33cf57a3e921509ff6e96342de19ecb5f2697c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,202 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 GroupsControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :groups_users + + def setup + @request.session[:user_id] = 1 + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_show + get :show, :id => 10 + assert_response :success + assert_template 'show' + end + + def test_show_invalid_should_return_404 + get :show, :id => 99 + assert_response 404 + end + + def test_new + get :new + assert_response :success + assert_template 'new' + assert_select 'input[name=?]', 'group[name]' + end + + def test_create + assert_difference 'Group.count' do + post :create, :group => {:name => 'New group'} + end + assert_redirected_to '/groups' + group = Group.first(:order => 'id DESC') + assert_equal 'New group', group.name + assert_equal [], group.users + end + + def test_create_and_continue + assert_difference 'Group.count' do + post :create, :group => {:name => 'New group'}, :continue => 'Create and continue' + end + assert_redirected_to '/groups/new' + group = Group.first(:order => 'id DESC') + assert_equal 'New group', group.name + end + + def test_create_with_failure + assert_no_difference 'Group.count' do + post :create, :group => {:name => ''} + end + assert_response :success + assert_template 'new' + end + + def test_edit + get :edit, :id => 10 + assert_response :success + assert_template 'edit' + + 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 + new_name = 'New name' + put :update, :id => 10, :group => {:name => new_name} + assert_redirected_to '/groups' + group = Group.find(10) + assert_equal new_name, group.name + end + + def test_update_with_failure + put :update, :id => 10, :group => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + assert_difference 'Group.count', -1 do + post :destroy, :id => 10 + end + assert_redirected_to '/groups' + end + + def test_add_users + assert_difference 'Group.find(10).users.count', 2 do + post :add_users, :id => 10, :user_ids => ['2', '3'] + end + end + + def test_xhr_add_users + assert_difference 'Group.find(10).users.count', 2 do + xhr :post, :add_users, :id => 10, :user_ids => ['2', '3'] + assert_response :success + assert_template 'add_users' + assert_equal 'text/javascript', response.content_type + end + assert_match /John Smith/, response.body + end + + def test_remove_user + assert_difference 'Group.find(10).users.count', -1 do + delete :remove_user, :id => 10, :user_id => '8' + end + end + + def test_xhr_remove_user + assert_difference 'Group.find(10).users.count', -1 do + xhr :delete, :remove_user, :id => 10, :user_id => '8' + assert_response :success + assert_template 'remove_user' + assert_equal 'text/javascript', response.content_type + end + end + + def test_new_membership + assert_difference 'Group.find(10).members.count' do + post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']} + end + end + + def test_xhr_new_membership + assert_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_match /OnlineStore/, response.body + end + + def test_xhr_new_membership_with_failure + assert_no_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership => { :project_id => 999, :role_ids => ['1', '2']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_match /alert/, response.body, "Alert message not sent" + end + + def test_edit_membership + assert_no_difference 'Group.find(10).members.count' do + post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']} + end + end + + def test_xhr_edit_membership + assert_no_difference 'Group.find(10).members.count' do + xhr :post, :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']} + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + end + + def test_destroy_membership + assert_difference 'Group.find(10).members.count', -1 do + post :destroy_membership, :id => 10, :membership_id => 6 + end + end + + def test_xhr_destroy_membership + assert_difference 'Group.find(10).members.count', -1 do + xhr :post, :destroy_membership, :id => 10, :membership_id => 6 + assert_response :success + assert_template 'destroy_membership' + assert_equal 'text/javascript', response.content_type + end + end + + def test_autocomplete_for_user + get :autocomplete_for_user, :id => 10, :q => 'smi', :format => 'js' + assert_response :success + assert_include 'John Smith', response.body + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf46c38f878a3a8e8d2ecb6560d5ca30550356b4.svn-base --- a/.svn/pristine/cf/cf46c38f878a3a8e8d2ecb6560d5ca30550356b4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,511 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - 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.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 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.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 => '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 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf83b2054e56a61378d31a5b59a9e7ef95f73db1.svn-base --- a/.svn/pristine/cf/cf83b2054e56a61378d31a5b59a9e7ef95f73db1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1091 +0,0 @@ -en-GB: - 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: [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: "%d/%m/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%d %B, %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 - - currency: - format: - format: "%u%n" - unit: "£" - - 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 (British)' - 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: 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 authorised 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_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})" - - 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})" - 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_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" - - 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: 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: Plain text mail (no HTML) - setting_host_name: Host name and path - setting_text_formatting: Text formatting - setting_wiki_compression: Wiki history compression - setting_feeds_limit: Feed content limit - setting_default_projects_public: New projects are public by default - 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: 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_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: 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: 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 - - 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_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 - - 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_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_total: Total - label_permissions: Permissions - label_current_status: Current status - label_new_statuses_allowed: New statuses allowed - label_all: all - 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: Personalise 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_greater_or_equal: '>=' - label_less_or_equal: '<=' - 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_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_day_plural: days - label_repository: 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_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_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:" - - 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 - - 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: task beginning this day - text_tip_issue_end_day: task ending this day - text_tip_issue_begin_end_day: task 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_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." - - 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 - 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_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 - 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 - 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 issue - one: 1 issue - other: "%{count} issues" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments - label_item_position: "%{position} of %{count}" - label_completed_versions: Completed versions - 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: all - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf93cd1a4b64064925ed170459c106faad562781.svn-base --- a/.svn/pristine/cf/cf93cd1a4b64064925ed170459c106faad562781.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingIssueCategoriesTest < ActionController::IntegrationTest - def test_issue_categories_scoped_under_project - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories.xml" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo', :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories.json" }, - { :controller => 'issue_categories', :action => 'index', - :project_id => 'foo', :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/projects/foo/issue_categories/new" }, - { :controller => 'issue_categories', :action => 'new', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories.xml" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo', :format => 'xml' } - ) - assert_routing( - { :method => 'post', :path => "/projects/foo/issue_categories.json" }, - { :controller => 'issue_categories', :action => 'create', - :project_id => 'foo', :format => 'json' } - ) - end - - def test_issue_categories - assert_routing( - { :method => 'get', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'show', :id => '1' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'show', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'show', :id => '1', - :format => 'json' } - ) - assert_routing( - { :method => 'get', :path => "/issue_categories/1/edit" }, - { :controller => 'issue_categories', :action => 'edit', :id => '1' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'update', :id => '1' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'update', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'put', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'update', :id => '1', - :format => 'json' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1.xml" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1', - :format => 'xml' } - ) - assert_routing( - { :method => 'delete', :path => "/issue_categories/1.json" }, - { :controller => 'issue_categories', :action => 'destroy', :id => '1', - :format => 'json' } - ) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cf9ad7baf9e113a573f644f31dff4cb65997570b.svn-base --- a/.svn/pristine/cf/cf9ad7baf9e113a573f644f31dff4cb65997570b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 GanttHelper - - def gantt_zoom_link(gantt, in_or_out) - case in_or_out - when :in - if gantt.zoom < 4 - link_to_content_update l(:text_zoom_in), - 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 - end - - when :out - if gantt.zoom > 1 - link_to_content_update l(:text_zoom_out), - 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 - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cfb6c51d7c06f79137cb3baca89fe82b05917998.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/cf/cfb6c51d7c06f79137cb3baca89fe82b05917998.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,97 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::RepositoryGitTest < RedminePmTest::TestCase + fixtures :projects, :users, :members, :roles, :member_roles + + GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" + + def test_anonymous_read_on_public_repo_with_permission_should_succeed + assert_success "ls-remote", git_url + end + + def test_anonymous_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + assert_failure "ls-remote", git_url + end + + def test_invalid_credentials_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls-remote", git_url + end + with_credentials "dlopper", "wrong" do + assert_failure "ls-remote", git_url + end + end + + def test_clone + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + assert_success "clone", git_url + end + end + end + + def test_write_commands + Role.find(2).add_permission! :commit_access + filename = random_filename + + Dir.mktmpdir do |dir| + assert_success "clone", git_url, dir + Dir.chdir(dir) do + f = File.new(File.join(dir, filename), "w") + f.write "test file content" + f.close + + with_credentials "dlopper", "foo" do + assert_success "add", filename + assert_success "commit -a --message Committing_a_file" + assert_success "push", git_url, "--all" + end + end + end + + Dir.mktmpdir do |dir| + assert_success "clone", git_url, dir + Dir.chdir(dir) do + assert File.exists?(File.join(dir, "#{filename}")) + end + end + end + + protected + + def execute(*args) + a = [GIT_BIN] + super a, *args + end + + def git_url(path=nil) + host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' + credentials = nil + if username && password + credentials = "#{username}:#{password}" + end + url = "http://#{credentials}@#{host}/git/ecookbook" + url << "/#{path}" if path + url + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cfc60690251a0006237e1c8a62505c246f4de941.svn-base --- a/.svn/pristine/cf/cfc60690251a0006237e1c8a62505c246f4de941.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -api.array :queries, api_meta(:total_count => @query_count, :offset => @offset, :limit => @limit) do - @queries.each do |query| - api.query do - api.id query.id - api.name query.name - api.is_public query.is_public - api.project_id query.project_id - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/cf/cfc7663e122e84036abc6410ab71cccdc11c960a.svn-base --- a/.svn/pristine/cf/cfc7663e122e84036abc6410ab71cccdc11c960a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 - include ERB::Util - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d03051bd71eb75793300dcbd48f633085578296c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d0/d03051bd71eb75793300dcbd48f633085578296c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d04345ff059a488f9272187f15a843b5609d167c.svn-base --- a/.svn/pristine/d0/d04345ff059a488f9272187f15a843b5609d167c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1099 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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 - - 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_project: Projekt - label_project_new: Neues Projekt - label_project_plural: Projekte - 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_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_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 - - status_active: aktiv - 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_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_caracters_maximum: "Max. %{count} Zeichen." - text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." - text_length_between: "Länge zwischen %{min} und %{max} Zeichen." - text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. - 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_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_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 - - 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 - - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d04e674c0740817a528515c15ffb33465b7523c4.svn-base --- a/.svn/pristine/d0/d04e674c0740817a528515c15ffb33465b7523c4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -== Redmine installation - -Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang -http://www.redmine.org/ - - -== Requirements - -* Ruby 1.8.7, 1.9.2 or 1.9.3 -* 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) - -Optional: -* SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) -* ImageMagick (to enable Gantt export to png images) - -== Installation - -1. Uncompress the program archive - -2. 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 - - 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 - a secret to be generated. Under the application main directory run: - rake generate_secret_token - -6. 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. - -7. 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. - - Assuming you run Redmine with a user named "redmine": - sudo chown -R redmine:redmine files log tmp public/plugin_assets - sudo chmod -R 755 files log tmp public/plugin_assets - -8. Test the installation by running the WEBrick web server - - Under the main application directory run: - ruby script/rails server -e production - - Once WEBrick has started, point your browser to http://localhost:3000/ - You should now see the application welcome page. - -9. 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d081bf55a7c6d79029b3f1816e9116ad715f9752.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d0/d081bf55a7c6d79029b3f1816e9116ad715f9752.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,168 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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, + :validate => false + + send :include, Redmine::Acts::Customizable::InstanceMethods + validate :validate_custom_field_values + after_save :save_custom_field_values + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + base.send :alias_method_chain, :reload, :custom_fields + end + + def available_custom_fields + CustomField.where("type = '#{self.class.name}CustomField'").sorted.all + 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) + values = values.stringify_keys + + custom_field_values.each do |custom_field_value| + key = custom_field_value.custom_field_id.to_s + if values.has_key?(key) + value = values[key] + if value.is_a?(Array) + value = value.reject(&:blank?).uniq + if value.empty? + value << '' + end + end + custom_field_value.value = value + end + end + @custom_field_values_changed = true + end + + def custom_field_values + @custom_field_values ||= available_custom_fields.collect do |field| + x = CustomFieldValue.new + x.custom_field = field + x.customized = self + if field.multiple? + values = custom_values.select { |v| v.custom_field == field } + if values.empty? + values << custom_values.build(:customized => self, :custom_field => field, :value => nil) + end + x.value = values.map(&:value) + else + cv = custom_values.detect { |v| v.custom_field == field } + cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil) + x.value = cv.value + end + x + end + 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 custom_field_value(c) + field_id = (c.is_a?(CustomField) ? c.id : c.to_i) + custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value) + end + + def validate_custom_field_values + if new_record? || custom_field_values_changed? + custom_field_values.each(&:validate_value) + end + end + + def save_custom_field_values + target_custom_values = [] + custom_field_values.each do |custom_field_value| + if custom_field_value.value.is_a?(Array) + custom_field_value.value.each do |v| + target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field && cv.value == v} + target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field, :value => v) + target_custom_values << target + end + else + target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field} + target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field) + target.value = custom_field_value.value + target_custom_values << target + end + end + self.custom_values = target_custom_values + custom_values.each(&:save) + @custom_field_values_changed = false + true + end + + def reset_custom_values! + @custom_field_values = nil + @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 + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d09adc22c24263d1cc302440c2cd187a5c123446.svn-base --- a/.svn/pristine/d0/d09adc22c24263d1cc302440c2cd187a5c123446.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::IssueStatusesTest < ActionController::IntegrationTest - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d0cb7ff899061c806d3cee6fe597edd800fee259.svn-base --- a/.svn/pristine/d0/d0cb7ff899061c806d3cee6fe597edd800fee259.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -require 'iconv' - -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| - begin - return Iconv.conv('UTF-8', encoding, str) - rescue Iconv::Failure - # do nothing here and try the next encoding - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d0/d0ef4d83b348265867fc9cc9e1a5ce1317d548e2.svn-base --- a/.svn/pristine/d0/d0ef4d83b348265867fc9cc9e1a5ce1317d548e2.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 FilesController < ApplicationController - menu_item :files - - before_filter :find_project_by_project_id - before_filter :authorize - - helper :sort - include SortHelper - - def index - sort_init 'filename', 'asc' - sort_update 'filename' => "#{Attachment.table_name}.filename", - 'created_on' => "#{Attachment.table_name}.created_on", - 'size' => "#{Attachment.table_name}.filesize", - 'downloads' => "#{Attachment.table_name}.downloads" - - @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] - @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse - render :layout => !request.xhr? - end - - def new - @versions = @project.versions.sort - end - - def create - container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) - attachments = Attachment.attach_files(container, params[:attachments]) - render_attachment_warning_if_needed(container) - - if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') - Mailer.attachments_added(attachments[:files]).deliver - end - redirect_to project_files_path(@project) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d135034b3f55af6d92fe20dfa38d8a5ebacd0d27.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d1/d135034b3f55af6d92fe20dfa38d8a5ebacd0d27.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,58 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 TimelogHelperTest < ActionView::TestCase + include TimelogHelper + include Redmine::I18n + include ActionView::Helpers::TextHelper + include ActionView::Helpers::DateHelper + include ERB::Util + + fixtures :projects, :roles, :enabled_modules, :users, + :repositories, :changesets, + :trackers, :issue_statuses, :issues, :versions, :documents, + :wikis, :wiki_pages, :wiki_contents, + :boards, :messages, + :attachments, + :enumerations + + def setup + super + end + + def test_activities_collection_for_select_options_should_return_array_of_activity_names_and_ids + activities = activity_collection_for_select_options + assert activities.include?(["Design", 9]) + assert activities.include?(["Development", 10]) + end + + def test_activities_collection_for_select_options_should_not_include_inactive_activities + activities = activity_collection_for_select_options + assert !activities.include?(["Inactive Activity", 14]) + end + + def test_activities_collection_for_select_options_should_use_the_projects_override + project = Project.find(1) + override_activity = TimeEntryActivity.create!({:name => "Design override", :parent => TimeEntryActivity.find_by_name("Design"), :project => project}) + + activities = activity_collection_for_select_options(nil, project) + assert !activities.include?(["Design", 9]), "System activity found in: " + activities.inspect + assert activities.include?(["Design override", override_activity.id]), "Override activity not found in: " + activities.inspect + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1901baccb91c0c319ba089375a69326e3aa0832.svn-base --- a/.svn/pristine/d1/d1901baccb91c0c319ba089375a69326e3aa0832.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= link_to l(:label_role_plural), roles_path %> » <%=h @role.name %>

    - -<%= labelled_form_for @role do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1b96a6dd664360b2334fda130aa63ab25dbd1be.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d1/d1b96a6dd664360b2334fda130aa63ab25dbd1be.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,38 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1beea22853d2b080b44752ca5d9ee2a41a0ab7d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d1/d1beea22853d2b080b44752ca5d9ee2a41a0ab7d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,443 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + include Redmine::SafeAttributes + + # Maximum length for repository identifiers + IDENTIFIER_MAX_LENGTH = 255 + + belongs_to :project + has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" + has_many :filechanges, :class_name => 'Change', :through => :changesets + + serialize :extra_info + + before_save :check_default + + # 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 + validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true + validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? } + 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 => /\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 + + safe_attributes 'identifier', + 'login', + 'password', + 'path_encoding', + 'log_encoding', + 'is_default' + + safe_attributes 'url', + :if => lambda {|repository, user| repository.new_record?} + + 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, *args) + attr_name = attribute_key_name.to_s + if attr_name == "log_encoding" + attr_name = "commit_logs_encoding" + end + super(attr_name, *args) + 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 + unless @scm + @scm = self.scm_adapter.new(url, root_url, + login, password, path_encoding) + if root_url.blank? && @scm.root_url.present? + update_attribute(:root_url, @scm.root_url) + end + end + @scm + end + + def scm_name + self.class.scm_name + end + + def name + if identifier.present? + identifier + elsif is_default? + l(:field_repository_is_default) + else + scm_name + end + end + + def identifier=(identifier) + super unless identifier_frozen? + end + + def identifier_frozen? + errors[:identifier].blank? && !(new_record? || identifier.blank?) + end + + def identifier_param + if is_default? + nil + elsif identifier.present? + identifier + else + id.to_s + end + end + + def <=>(repository) + if is_default? + -1 + elsif repository.is_default? + 1 + else + identifier.to_s <=> repository.identifier.to_s + end + end + + def self.find_by_identifier_param(param) + if param.to_s =~ /^\d+$/ + find_by_id(param) + else + find_by_identifier(param) + end + end + + # TODO: should return an empty hash instead of nil to avoid many ||{} + def extra_info + h = read_attribute(:extra_info) + h.is_a?(Hash) ? h : nil + 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) + entries = scm.entries(path, identifier) + load_entries_changesets(entries) + entries + 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? + s = name.to_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.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. + reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"). + limit(limit). + preload(:user). + all + else + filechanges. + where("path = ?", path.with_leading_slash). + reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"). + limit(limit). + preload(:changeset => :user). + 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.where(:committer => committer).includes(:user).first + 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).all.each do |project| + project.repositories.each do |repository| + begin + 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 + 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 + + def set_as_default? + new_record? && project && Repository.where(:project_id => project.id).empty? + end + + protected + + def check_default + if !is_default? && set_as_default? + self.is_default = true + end + if is_default? && is_default_changed? + Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id]) + end + end + + def load_entries_changesets(entries) + if entries + entries.each do |entry| + if entry.lastrev && entry.lastrev.identifier + entry.changeset = find_changeset_by_name(entry.lastrev.identifier) + end + end + end + end + + private + + # Deletes repository data + def clear_changesets + cs = Changeset.table_name + ch = Change.table_name + ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}" + cp = "#{table_name_prefix}changeset_parents#{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 #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") + connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") + clear_extra_info_of_changesets + end + + def clear_extra_info_of_changesets + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1cb23e4b2849d91c4eed462a543696732f1999f.svn-base --- a/.svn/pristine/d1/d1cb23e4b2849d91c4eed462a543696732f1999f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -class AddViewIssuesPermission < ActiveRecord::Migration - def self.up - Role.find(:all).each do |r| - r.add_permission!(:view_issues) - end - end - - def self.down - Role.find(:all).each do |r| - r.remove_permission!(:view_issues) - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1d2987ccc86a4dcc8714583c2848d2db3777782.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d1/d1d2987ccc86a4dcc8714583c2848d2db3777782.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,49 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 Redmine::I18n + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1f54456cad7de7f5bbac6928b60b16922921a2a.svn-base --- a/.svn/pristine/d1/d1f54456cad7de7f5bbac6928b60b16922921a2a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 -unsubscribe: - default: 1 -password_min_length: - format: int - default: 8 -# Maximum lifetime of user sessions in minutes -session_lifetime: - format: int - default: 0 -# User session timeout in minutes -session_timeout: - format: int - default: 0 -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_cross_project_ref: - default: 0 -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 -# Enables subtasks to be in other projects -cross_project_subtasks: - default: 'tree' -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 -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 - 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 -jsonp_enabled: - default: 0 -default_notification_option: - default: 'only_my_events' -emails_header: - default: '' -thumbnails_enabled: - default: 0 -thumbnails_size: - format: int - default: 100 -non_working_week_days: - serialized: true - default: - - '6' - - '7' diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d1/d1f6270c9fecf89d5ee1cb71ef6b5a350394effe.svn-base --- a/.svn/pristine/d1/d1f6270c9fecf89d5ee1cb71ef6b5a350394effe.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module MyHelper -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d2/d20eef4d6912b004ef4d39c2028db24d0652606b.svn-base --- a/.svn/pristine/d2/d20eef4d6912b004ef4d39c2028db24d0652606b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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.find :all, :include => [:attachments, :category] - 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.find(:all, :order => "created_on DESC") - 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 :action => 'index', :project_id => @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 :action => 'show', :id => @document - else - render :action => 'edit' - end - end - - def destroy - @document.destroy if request.delete? - redirect_to :controller => 'documents', :action => 'index', :project_id => @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 :action => 'show', :id => @document - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d2/d22f32dc9c0be86582aa704e517560621de1823c.svn-base --- a/.svn/pristine/d2/d22f32dc9c0be86582aa704e517560621de1823c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,265 +0,0 @@ ---- -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 -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 -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 -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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d2/d27387abf40cb290649a11cf867b6086844c45be.svn-base --- a/.svn/pristine/d2/d27387abf40cb290649a11cf867b6086844c45be.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d2/d290eee74cbe20f80a2e1db7f27a9b1e012b2872.svn-base --- a/.svn/pristine/d2/d290eee74cbe20f80a2e1db7f27a9b1e012b2872.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 "$('#search-input').focus()" %> -<%= 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(e.event_title, :length => 255), @tokens), e.event_url %>
    -
    <%= highlight_tokens(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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d2/d2d75f5282e4774cc6782dd489046bdab3498c66.svn-base --- a/.svn/pristine/d2/d2d75f5282e4774cc6782dd489046bdab3498c66.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -<%= error_messages_for 'member' %> -<% roles = Role.find_all_givable - members = @project.member_principals.find(:all, :include => [:roles, :principal]).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 %> -
    - -<% principals = Principal.active.not_member_of(@project).all(:limit => 100, :order => 'type, login, lastname ASC') %> - -
    -<% if roles.any? && principals.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', 'principals', '#{ escape_javascript autocomplete_project_memberships_path(@project) }')" %> - -
    - <%= principals_check_box_tags 'membership[user_ids][]', principals %> -
    - -

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

    - -

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

    -
    - <% end %> -<% end %> -
    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d3/d39d55792569592e631ccb43cfe454592c67567f.svn-base --- a/.svn/pristine/d3/d39d55792569592e631ccb43cfe454592c67567f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -desc 'Updates and checks locales against en.yml' -task :locales do - %w(locales:update locales:check_interpolation).collect do |task| - Rake::Task[task].invoke - end -end - -namespace :locales do - desc 'Updates language files based on en.yml content (only works for new top level keys).' - task :update do - dir = ENV['DIR'] || './config/locales' - - en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] - - files = Dir.glob(File.join(dir,'*.{yaml,yml}')) - files.sort.each do |file| - puts "Updating file #{file}" - file_strings = YAML.load(File.read(file)) - file_strings = file_strings[file_strings.keys.first] - - missing_keys = en_strings.keys - file_strings.keys - next if missing_keys.empty? - - puts "==> Missing #{missing_keys.size} keys (#{missing_keys.join(', ')})" - lang = File.open(file, 'a') - - missing_keys.each do |key| - {key => en_strings[key]}.to_yaml.each_line do |line| - next if line =~ /^---/ || line.empty? - puts " #{line}" - lang << " #{line}" - end - end - - lang.close - end - end - - desc 'Checks interpolation arguments in locals against en.yml' - task :check_interpolation do - dir = ENV['DIR'] || './config/locales' - en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] - 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 = file_strings[file_strings.keys.first] - - file_strings.each do |key, string| - next unless string.is_a?(String) - string.scan /%\{\w+\}/ do |match| - unless en_strings[key].nil? || en_strings[key].include?(match) - puts "#{file}: #{key} uses #{match} not found in en.yml" - end - end - end - end - end - - desc <<-END_DESC -Removes a translation string from all locale file (only works for top-level childless non-multiline keys, probably doesn\'t work on windows). - -This task does not work on Ruby 1.8.6. -You need to use Ruby 1.8.7 or later. - -Options: - key=key_1,key_2 Comma-separated list of keys to delete - skip=en,de Comma-separated list of locale files to ignore (filename without extension) -END_DESC - - task :remove_key do - dir = ENV['DIR'] || './config/locales' - files = Dir.glob(File.join(dir,'*.yml')) - skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil - deletes = ENV['key'] ? Regexp.union(ENV['key'].split(',')) : nil - # Ignore multiline keys (begin with | or >) and keys with children (nothing meaningful after :) - delete_regex = /\A #{deletes}: +[^\|>\s#].*\z/ - - files.each do |path| - # Skip certain locales - (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips - puts "Deleting selected keys from #{path}" - orig_content = File.open(path, 'r') {|file| file.read} - File.open(path, 'w') {|file| orig_content.each_line {|line| file.puts line unless line.chomp =~ delete_regex}} - end - end - - desc <<-END_DESC -Adds a new top-level translation string to all locale file (only works for childless keys, probably doesn\'t work on windows, doesn't check for duplicates). - -Options: - key="some_key=foo" - key1="another_key=bar" - key_fb="foo=bar" Keys to add in the form key=value, every option of the form key[,\\d,_*] will be recognised - skip=en,de Comma-separated list of locale files to ignore (filename without extension) -END_DESC - - task :add_key do - dir = ENV['DIR'] || './config/locales' - files = Dir.glob(File.join(dir,'*.yml')) - skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil - keys_regex = /\Akey(\d+|_.+)?\z/ - adds = ENV.reject {|k,v| !(k =~ keys_regex)}.values.collect {|v| Array.new v.split("=",2)} - key_list = adds.collect {|v| v[0]}.join(", ") - - files.each do |path| - # Skip certain locales - (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips - # TODO: Check for dupliate/existing keys - puts "Adding #{key_list} to #{path}" - File.open(path, 'a') do |file| - adds.each do |kv| - Hash[*kv].to_yaml.each_line do |line| - file.puts " #{line}" unless (line =~ /^---/ || line.empty?) - end - end - end - end - end - - desc 'Duplicates a key. Exemple rake locales:dup key=foo new_key=bar' - task :dup do - dir = ENV['DIR'] || './config/locales' - files = Dir.glob(File.join(dir,'*.yml')) - skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil - key = ENV['key'] - new_key = ENV['new_key'] - abort "Missing key argument" if key.blank? - abort "Missing new_key argument" if new_key.blank? - - files.each do |path| - # Skip certain locales - (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips - puts "Adding #{new_key} to #{path}" - - strings = File.read(path) - unless strings =~ /^( #{key}: .+)$/ - puts "Key not found in #{path}" - next - end - line = $1 - - File.open(path, 'a') do |file| - file.puts(line.sub(key, new_key)) - end - end - end - - desc 'Check parsing yaml by psych library on Ruby 1.9.' - - # On Fedora 12 and 13, if libyaml-devel is available, - # in case of installing by rvm, - # Ruby 1.9 default yaml library is psych. - - task :check_parsing_by_psych do - begin - require 'psych' - parser = Psych::Parser.new - dir = ENV['DIR'] || './config/locales' - files = Dir.glob(File.join(dir,'*.yml')) - files.sort.each do |filename| - next if File.directory? filename - puts "parsing #{filename}..." - begin - parser.parse File.open(filename) - rescue Exception => e1 - puts(e1.message) - puts("") - end - end - rescue Exception => e - puts(e.message) - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d3/d3a6378c9406ba10d03e5d3c6c1f099aa9094db0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d3/d3a6378c9406ba10d03e5d3c6c1f099aa9094db0.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,154 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d3/d3b7ec6980c63f6b117346516bdccd11679e27f3.svn-base --- a/.svn/pristine/d3/d3b7ec6980c63f6b117346516bdccd11679e27f3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -<%= error_messages_for 'project' %> - -
    - -

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

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

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

    -<% end %> - -

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

    -

    <%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen? %> -<% 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 %>

    -<%= 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 %> - diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d433c2a67bf6894f8afced62c37440b82b2851a7.svn-base --- a/.svn/pristine/d4/d433c2a67bf6894f8afced62c37440b82b2851a7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d43d36e594d16de75bb067e8d84f9828c830aa58.svn-base --- a/.svn/pristine/d4/d43d36e594d16de75bb067e8d84f9828c830aa58.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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(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;" %> - -<% if @groups.present? %> - -<%= select_tag 'group_id', content_tag('option') + 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 -%> - "> - - - - - - - - - -<% 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) %> - <%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> -
    -
    -

    <%= pagination_links_full @user_pages, @user_count %>

    - -<% html_title(l(:label_user_plural)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d4436f41c0a822f872e3e0929255293804932226.svn-base --- a/.svn/pristine/d4/d4436f41c0a822f872e3e0929255293804932226.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -

    -<%= label_tag "user_mail_notification", l(:description_user_mail_notification), :class => "hidden-for-sighted" %> -<%= select_tag( - 'user[mail_notification]', - options_for_select( - user_mail_notification_options(@user), @user.mail_notification), - :onchange => 'if (this.value == "selected") {$("#notified-projects").show();} else {$("#notified-projects").hide();}' - ) %> -

    -<%= content_tag 'div', :id => 'notified-projects', :style => (@user.mail_notification == 'selected' ? '' : 'display:none;') do %> - <%= render_project_nested_lists(@user.projects) do |project| - content_tag('label', - check_box_tag( - 'notified_project_ids[]', - project.id, - @user.notified_projects_ids.include?(project.id) - ) + ' ' + h(project.name) - ) - end %> -

    <%= l(:text_user_mail_option) %>

    -<% end %> - -<%= fields_for :pref, @user.pref do |pref_fields| %> -

    - <%= pref_fields.check_box :no_self_notified %> - -

    -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d4471ce43ecc0825edcafc2a88f57919d32be567.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d4/d4471ce43ecc0825edcafc2a88f57919d32be567.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,164 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 RepositoriesFilesystemControllerTest < ActionController::TestCase + tests RepositoriesController + + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :enabled_modules + + REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s + PRJ_ID = 3 + + def setup + @ruby19_non_utf8_pass = + (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') + User.current = nil + Setting.enabled_scm << 'Filesystem' unless Setting.enabled_scm.include?('Filesystem') + @project = Project.find(PRJ_ID) + @repository = Repository::Filesystem.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => '' + ) + 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 => 'Filesystem' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Filesystem, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_browse_root + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID + 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 + + assert_no_tag 'input', :attributes => {:name => 'rev'} + assert_no_tag 'a', :content => 'Statistics' + assert_no_tag 'a', :content => 'Atom' + end + + def test_show_no_extension + get :entry, :id => PRJ_ID, :path => repository_path_hash(['test'])[:param] + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /TEST CAT/ } + end + + def test_entry_download_no_extension + get :raw, :id => PRJ_ID, :path => repository_path_hash(['test'])[:param] + assert_response :success + assert_equal 'application/octet-stream', @response.content_type + end + + def test_show_non_ascii_contents + with_settings :repositories_encodings => 'UTF-8,EUC-JP' do + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['japanese', 'euc-jp.txt'])[:param] + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '2', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /japanese/ } + if @ruby19_non_utf8_pass + puts "TODO: show repository file contents test fails in Ruby 1.9 " + + "and Encoding.default_external is not UTF-8. " + + "Current value is '#{Encoding.default_external.to_s}'" + else + str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" + str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding) + assert_tag :tag => 'th', + :content => '3', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /#{str_japanese}/ } + end + end + end + + def test_show_utf16 + 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 + assert_tag :tag => 'th', + :content => '2', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /japanese/ } + end + end + + def test_show_text_file_should_send_if_too_big + with_settings :file_max_size_displayed => 1 do + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['japanese', 'big-file.txt'])[:param] + assert_response :success + assert_equal 'text/plain', @response.content_type + end + end + + def test_destroy_valid_repository + @request.session[:user_id] = 1 # admin + + 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::Filesystem.create!( + :project => @project, + :url => "/invalid", + :path_encoding => '' + ) + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + else + puts "Filesystem test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d47d7f343b4fb4b27abd5597b1da0b571621891c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d4/d47d7f343b4fb4b27abd5597b1da0b571621891c.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d47e9d5ab984bc97cf6014ec52e8615b3731e510.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d4/d47e9d5ab984bc97cf6014ec52e8615b3731e510.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,65 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false).first + assert WorkflowTransition.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true).first + assert WorkflowTransition.where(:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false).first + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d4b58d822f95056e91ecc443b37eb8146638e83d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d4/d4b58d822f95056e91ecc443b37eb8146638e83d.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class SearchController < ApplicationController + before_filter :find_optional_project + + def index + @question = params[:q] || "" + @question.strip! + @all_words = params[:all_words] ? params[:all_words].present? : true + @titles_only = params[:titles_only] ? params[:titles_only].present? : false + + projects_to_search = + case params[:scope] + when 'all' + nil + when 'my_projects' + User.current.memberships.collect(&:project) + when 'subprojects' + @project ? (@project.self_and_descendants.active.all) : nil + else + @project + end + + offset = nil + begin; offset = params[:offset].to_time if params[:offset]; rescue; end + + # quick jump to an issue + if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) + redirect_to issue_path(issue) + return + end + + @object_types = Redmine::Search.available_search_types.dup + if projects_to_search.is_a? Project + # don't search projects + @object_types.delete('projects') + # only show what the user is allowed to view + @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} + end + + @scope = @object_types.select {|t| params[t]} + @scope = @object_types if @scope.empty? + + # extract tokens from the question + # eg. hello "bye bye" => ["hello", "bye bye"] + @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} + # tokens must be at least 2 characters long + @tokens = @tokens.uniq.select {|w| w.length > 1 } + + if !@tokens.empty? + # no more than 5 tokens to search for + @tokens.slice! 5..-1 if @tokens.size > 5 + + @results = [] + @results_by_type = Hash.new {|h,k| h[k] = 0} + + limit = 10 + @scope.each do |s| + r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search, + :all_words => @all_words, + :titles_only => @titles_only, + :limit => (limit+1), + :offset => offset, + :before => params[:previous].nil?) + @results += r + @results_by_type[s] += c + end + @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} + if params[:previous].nil? + @pagination_previous_date = @results[0].event_datetime if offset && @results[0] + if @results.size > limit + @pagination_next_date = @results[limit-1].event_datetime + @results = @results[0, limit] + end + else + @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] + if @results.size > limit + @pagination_previous_date = @results[-(limit)].event_datetime + @results = @results[-(limit), limit] + end + end + else + @question = "" + end + render :layout => false if request.xhr? + end + +private + def find_optional_project + return true unless params[:id] + @project = Project.find(params[:id]) + check_project_privacy + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d4/d4dcf9a5e7b6ef2ec5289c5f08ea6b3b53a0b6cf.svn-base --- a/.svn/pristine/d4/d4dcf9a5e7b6ef2ec5289c5f08ea6b3b53a0b6cf.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -class PopulateMemberRoles < ActiveRecord::Migration - def self.up - MemberRole.delete_all - Member.find(: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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d5/d585690cfe198e8a0231b22d2d8e4ddaede4dbca.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d5/d585690cfe198e8a0231b22d2d8e4ddaede4dbca.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,42 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d66065a9dcfc99aec6ff539c6e6b8b39441b8dc8.svn-base --- a/.svn/pristine/d6/d66065a9dcfc99aec6ff539c6e6b8b39441b8dc8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -<%= form_tag(project_enumerations_path(@project), :method => :put, :class => "tabular") do %> - - - - - - <% TimeEntryActivity.new.available_custom_fields.each do |value| %> - - <% end %> - - - - <% @project.activities(true).each do |enumeration| %> - <%= fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %> - - - - <% enumeration.custom_field_values.each do |value| %> - - <% end %> - - - <% end %> - <% end %> -
    <%= l(:field_name) %><%= l(:enumeration_system_activity) %><%= h value.name %><%= l(:field_active) %>
    - <%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %> - <%= h(enumeration) %> - <%= checked_image !enumeration.project %> - <%= custom_field_tag "enumerations[#{enumeration.id}]", value %> - - <%= ff.check_box :active %> -
    - -
    -<%= link_to(l(:button_reset), project_enumerations_path(@project), - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del') %> -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d66e175c15ed6eac30ec3dcf7b51f04bd3ad7599.svn-base --- a/.svn/pristine/d6/d66e175c15ed6eac30ec3dcf7b51f04bd3ad7599.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,319 +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_height = 18 - - headers_height = header_height - show_weeks = false - show_days = false - - if @gantt.zoom > 1 - show_weeks = true - headers_height = 2 * header_height - if @gantt.zoom > 2 - show_days = true - headers_height = 3 * header_height - end - end - - # Width of the entire chart - g_width = ((@gantt.date_to - @gantt.date_from + 1) * zoom).to_i - @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 %> - - - - - - - -
    - <% - style = "" - style += "position:relative;" - style += "height: #{t_height + 24}px;" - style += "width: #{subject_width + 1}px;" - %> - <%= content_tag(:div, :style => style) do %> - <% - style = "" - style += "right:-2px;" - style += "width: #{subject_width}px;" - style += "height: #{headers_height}px;" - style += 'background: #eee;' - %> - <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> - <% - style = "" - style += "right:-2px;" - style += "width: #{subject_width}px;" - style += "height: #{t_height}px;" - style += 'border-left: 1px solid #c0c0c0;' - style += 'overflow: hidden;' - %> - <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> - <%= content_tag(:div, :class => "gantt_subjects") do %> - <%= @gantt.subjects.html_safe %> - <% end %> - <% end %> - -
    -<% - style = "" - style += "width: #{g_width - 1}px;" - style += "height: #{headers_height}px;" - style += 'background: #eee;' -%> -<%= content_tag(:div, ' '.html_safe, :style => style, :class => "gantt_hdr") %> - -<% ###### Months headers ###### %> -<% - month_f = @gantt.date_from - left = 0 - height = (show_weeks ? header_height : header_height + g_height) -%> -<% @gantt.months.times do %> - <% - width = (((month_f >> 1) - month_f) * zoom - 1).to_i - style = "" - style += "left: #{left}px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> - <%= 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}" %> - <% end %> - <% - left = left + width + 1 - month_f = month_f >> 1 - %> -<% end %> - -<% ###### Weeks headers ###### %> -<% if show_weeks %> - <% - left = 0 - height = (show_days ? header_height - 1 : header_height - 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 - style = "" - style += "left: #{left}px;" - style += "top: 19px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, ' '.html_safe, - :style => style, :class => "gantt_hdr") %> - <% 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).to_i - style = "" - style += "left: #{left}px;" - style += "top: 19px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - %> - <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> - <%= content_tag(:small) do %> - <%= week_f.cweek if width >= 16 %> - <% end %> - <% end %> - <% - left = left + width + 1 - week_f = week_f + 7 - %> - <% end %> -<% end %> - -<% ###### Days headers ####### %> -<% if show_days %> - <% - left = 0 - height = g_height + header_height - 1 - wday = @gantt.date_from.cwday - %> - <% (@gantt.date_to - @gantt.date_from + 1).to_i.times do %> - <% - width = zoom - 1 - style = "" - style += "left: #{left}px;" - style += "top:37px;" - style += "width: #{width}px;" - style += "height: #{height}px;" - style += "font-size:0.7em;" - clss = "gantt_hdr" - clss << " nwday" if @gantt.non_working_week_days.include?(wday) - %> - <%= content_tag(:div, :style => style, :class => clss) do %> - <%= day_letter(wday) %> - <% end %> - <% - 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 %> - <% - today_left = (((Date.today - @gantt.date_from + 1) * zoom).floor() - 1).to_i - style = "" - style += "position: absolute;" - style += "height: #{g_height}px;" - style += "top: #{headers_height + 1}px;" - style += "left: #{today_left}px;" - style += "width:10px;" - style += "border-left: 1px dashed red;" - %> - <%= 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") %> -
    -
    - - - - - - -
    - <%= 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)) -%> - -<% 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d67c3b233c2de5e381b50b04ecf28037f5dff73a.svn-base --- a/.svn/pristine/d6/d67c3b233c2de5e381b50b04ecf28037f5dff73a.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 teardown - Setting.clear_cache - 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_create_already_existing - post :create_project_repository, :id => 1, - :vendor => 'Subversion', - :repository => { :url => 'file:///create/project/repository/subproject2'} - - assert_response :conflict - end - - def test_create_with_failure - post :create_project_repository, :id => 4, - :vendor => 'Subversion', - :repository => { :url => 'invalid url'} - - assert_response :unprocessable_entity - end - - def test_fetch_changesets - Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true) - get :fetch_changesets - assert_response :success - end - - def test_fetch_changesets_one_project_by_identifier - Repository::Subversion.any_instance.expects(:fetch_changesets).once.returns(true) - get :fetch_changesets, :id => 'ecookbook' - assert_response :success - end - - def test_fetch_changesets_one_project_by_id - Repository::Subversion.any_instance.expects(:fetch_changesets).once.returns(true) - get :fetch_changesets, :id => '1' - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d686576cc5313c6c5ff9ec468ab9b2b763d728a1.svn-base --- a/.svn/pristine/d6/d686576cc5313c6c5ff9ec468ab9b2b763d728a1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -# Information -# -# PDF_EPS class from Valentin Schmidt ported to ruby by Thiago Jackiw (tjackiw@gmail.com) -# working for Mingle LLC (www.mingle.com) -# Release Date: July 13th, 2006 -# -# Description -# -# This script allows to embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files. -# Only vector drawing is supported, not text or bitmap. Although the script was successfully -# tested with various AI format versions, best results are probably achieved with files that -# were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2). -# -# ImageEps(string file, float x, float y [, float w [, float h [, string link [, boolean useBoundingBox]]]]) -# -# Same parameters as for regular FPDF::Image() method, with an additional one: -# -# useBoundingBox: specifies whether to position the bounding box (true) or the complete canvas (false) -# at location (x,y). Default value is true. -# -# First added to the Ruby FPDF distribution in 1.53c -# -# Usage is as follows: -# -# require 'fpdf' -# require 'fpdf_eps' -# pdf = FPDF.new -# pdf.extend(PDF_EPS) -# pdf.ImageEps(...) -# -# This allows it to be combined with other extensions, such as the bookmark -# module. - -module PDF_EPS - def ImageEps(file, x, y, w=0, h=0, link='', use_bounding_box=true) - data = nil - if File.exists?(file) - File.open(file, 'rb') do |f| - data = f.read() - end - else - Error('EPS file not found: '+file) - end - - # Find BoundingBox param - regs = data.scan(/%%BoundingBox: [^\r\n]*/m) - regs << regs[0].gsub(/%%BoundingBox: /, '') - if regs.size > 1 - tmp = regs[1].to_s.split(' ') - @x1 = tmp[0].to_i - @y1 = tmp[1].to_i - @x2 = tmp[2].to_i - @y2 = tmp[3].to_i - else - Error('No BoundingBox found in EPS file: '+file) - end - f_start = data.index('%%EndSetup') - f_start = data.index('%%EndProlog') if f_start === false - f_start = data.index('%%BoundingBox') if f_start === false - - data = data.slice(f_start, data.length) - - f_end = data.index('%%PageTrailer') - f_end = data.index('showpage') if f_end === false - data = data.slice(0, f_end) if f_end - - # save the current graphic state - out('q') - - k = @k - - # Translate - if use_bounding_box - dx = x*k-@x1 - dy = @hPt-@y2-y*k - else - dx = x*k - dy = -y*k - end - tm = [1,0,0,1,dx,dy] - out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', - tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) - - if w > 0 - scale_x = w/((@x2-@x1)/k) - if h > 0 - scale_y = h/((@y2-@y1)/k) - else - scale_y = scale_x - h = (@y2-@y1)/k * scale_y - end - else - if h > 0 - scale_y = $h/((@y2-@y1)/$k) - scale_x = scale_y - w = (@x2-@x1)/k * scale_x - else - w = (@x2-@x1)/k - h = (@y2-@y1)/k - end - end - - if !scale_x.nil? - # Scale - tm = [scale_x,0,0,scale_y,0,@hPt*(1-scale_y)] - out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', - tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])) - end - - data.split(/\r\n|[\r\n]/).each do |line| - next if line == '' || line[0,1] == '%' - len = line.length - # next if (len > 2 && line[len-2,len] != ' ') - cmd = line[len-2,len].strip - case cmd - when 'm', 'l', 'v', 'y', 'c', 'k', 'K', 'g', 'G', 's', 'S', 'J', 'j', 'w', 'M', 'd': - out(line) - - when 'L': - line[len-1,len]='l' - out(line) - - when 'C': - line[len-1,len]='c' - out(line) - - when 'f', 'F': - out('f*') - - when 'b', 'B': - out(cmd + '*') - end - end - - # restore previous graphic state - out('Q') - Link(x,y,w,h,link) if link - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d6d2d7c49f7779d8e6b7df27e7cb7703684fb6de.svn-base --- a/.svn/pristine/d6/d6d2d7c49f7779d8e6b7df27e7cb7703684fb6de.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -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.find(:all).collect(&:id) - Project.find(:all).each do |project| - project.tracker_ids = tracker_ids - end - end - - def self.down - drop_table :projects_trackers - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d6f5abe81850d0a2184399707a466120a7c0f2ab.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d6/d6f5abe81850d0a2184399707a466120a7c0f2ab.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,142 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + include Redmine::SubclassFactory + + default_scope :order => "#{Enumeration.table_name}.position ASC" + + belongs_to :project + + acts_as_list :scope => 'type = \'#{type}\'' + acts_as_customizable + acts_as_tree :order => "#{Enumeration.table_name}.position ASC" + + before_destroy :check_integrity + before_save :check_default + + attr_protected :type + + validates_presence_of :name + validates_uniqueness_of :name, :scope => [:type, :project_id] + validates_length_of :name, :maximum => 30 + + 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 + # 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? + where(:is_default => true, :type => 'Enumeration').first + else + # STI classes are + where(:is_default => true).first + 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 => 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 + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d6/d6fa2ab030f2e7ebc86e650e4acc6d8328176458.svn-base --- a/.svn/pristine/d6/d6fa2ab030f2e7ebc86e650e4acc6d8328176458.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d71cf18bdc19861ad8ec64f239467d87c9f0a844.svn-base --- a/.svn/pristine/d7/d71cf18bdc19861ad8ec64f239467d87c9f0a844.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1088 +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_hours: - one: "1 hour" - other: "%{count} hours" - 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_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_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}) - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d73cc4282b0675ac6625b48691a08efc047fa105.svn-base --- a/.svn/pristine/d7/d73cc4282b0675ac6625b48691a08efc047fa105.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d7491fe30d43b3102b4c91e4fb373cefd28da79d.svn-base --- a/.svn/pristine/d7/d7491fe30d43b3102b4c91e4fb373cefd28da79d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d76383ae6cf596872815e185d754067b3a322dd7.svn-base --- a/.svn/pristine/d7/d76383ae6cf596872815e185d754067b3a322dd7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -
    -<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %> -
    - -

    <%=l(:label_project_plural)%>

    - -<%= form_tag({}, :method => :get) do %> -
    <%= l(:label_filter_plural) %> - -<%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> - -<%= text_field_tag 'name', params[:name], :size => 30 %> -<%= submit_tag l(:button_apply), :class => "small", :name => nil %> -<%= link_to l(:button_clear), {:controller => 'admin', :action => 'projects'}, :class => 'icon icon-reload' %> -
    -<% end %> -  - -
    - - - - - - - - -<% project_tree(@projects) do |project, level| %> - <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> - - - - - -<% end %> - -
    <%=l(:label_project)%><%=l(:field_is_public)%><%=l(:field_created_on)%>
    <%= link_to_project(project, {:action => (project.active? ? 'settings' : 'show')}, :title => project.short_description) %><%= checked_image project.is_public? %><%= format_date(project.created_on) %> - <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock') unless project.archived? %> - <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if project.archived? && (project.parent.nil? || !project.parent.archived?) %> - <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %> - <%= link_to(l(:button_delete), project_path(project), :method => :delete, :class => 'icon icon-del') %> -
    -
    - -<% html_title(l(:label_project_plural)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d78a39e9b58d0cccbfb485c04f269367618abd3f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d7/d78a39e9b58d0cccbfb485c04f269367618abd3f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,108 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d7945f68bd8e0b1c181726c04ed073f7f409138b.svn-base Binary file .svn/pristine/d7/d7945f68bd8e0b1c181726c04ed073f7f409138b.svn-base has changed diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d7/d7b81552048782a1cfa0ef03ddf10a70a1bb00e7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d7/d7b81552048782a1cfa0ef03ddf10a70a1bb00e7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d8196202011390bb65472303826d339a44bedbed.svn-base --- a/.svn/pristine/d8/d8196202011390bb65472303826d339a44bedbed.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +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 - - def environment - s = "Environment:\n" - s << [ - ["Redmine version", Redmine::VERSION], - ["Ruby version", "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"], - ["Rails version", Rails::VERSION::STRING], - ["Environment", Rails.env], - ["Database adapter", ActiveRecord::Base.connection.adapter_name] - ].map {|info| " %-30s %s" % info}.join("\n") - s << "\nRedmine plugins:\n" - - plugins = Redmine::Plugin.all - if plugins.any? - s << plugins.map {|plugin| " %-30s %s" % [plugin.id.to_s, plugin.version.to_s]}.join("\n") - else - s << " no plugin installed" - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d820b362da40aaa2b6531056312ceeddee3d097b.svn-base --- a/.svn/pristine/d8/d820b362da40aaa2b6531056312ceeddee3d097b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RepositoriesCvsControllerTest < ActionController::TestCase - tests RepositoriesController - - 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 - 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_get_new - @request.session[:user_id] = 1 - @project.repository.destroy - get :new, :project_id => 'subproject1', :repository_scm => 'Cvs' - assert_response :success - assert_template 'new' - assert_kind_of Repository::Cvs, 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 - - 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 => repository_path_hash(['images'])[:param] - 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 => repository_path_hash(['images'])[:param], - :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 => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] - 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 => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], - :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 => repository_path_hash(['sources', '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 :entry, :id => PRJ_ID, - :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], - :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 => 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 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 => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] - assert_response :success - assert_template 'annotate' - - # 1.1 line - assert_select 'tr' do - assert_select 'th.line-num', :text => '21' - assert_select 'td.revision', :text => /1.1/ - assert_select 'td.author', :text => /LANG/ - end - # 1.2 line - assert_select 'tr' do - assert_select 'th.line-num', :text => '32' - assert_select 'td.revision', :text => /1.2/ - assert_select 'td.author', :text => /LANG/ - 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::Cvs.create!( - :project => Project.find(PRJ_ID), - :root_url => "/invalid", - :url => MODULE_NAME, - :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 "CVS test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d8240e0ef2bf417ed03d747eb93bd62ad19a2085.svn-base --- a/.svn/pristine/d8/d8240e0ef2bf417ed03d747eb93bd62ad19a2085.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1087 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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 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_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ů 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: 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} podú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 anotová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 - 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}) - 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 úklů 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 podú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 - 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 - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d84028e9377754df4951a789fc085f8e2eb7bb46.svn-base --- a/.svn/pristine/d8/d84028e9377754df4951a789fc085f8e2eb7bb46.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,287 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require "digest/md5" - -class Attachment < ActiveRecord::Base - belongs_to :container, :polymorphic => true - belongs_to :author, :class_name => "User", :foreign_key => "author_id" - - validates_presence_of :filename, :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) - if new_record? && disk_filename.blank? - self.disk_filename = Attachment.disk_filename(filename) - end - 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) - logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") - 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_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) - container && container.attachments_visible?(user) - end - - def deletable?(user=User.current) - container && container.attachments_deletable?(user) - 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 - - 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 an ASCII or hashed filename - def self.disk_filename(filename) - 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, "#{timestamp}_#{ascii}")) - timestamp.succ! - end - "#{timestamp}_#{ascii}" - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d8639e1305d3a5ce3a25132171c278b1ff63a8b1.svn-base --- a/.svn/pristine/d8/d8639e1305d3a5ce3a25132171c278b1ff63a8b1.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d872c59c53fb6c77d47a3fa010a7ea331d0c6dee.svn-base --- a/.svn/pristine/d8/d872c59c53fb6c77d47a3fa010a7ea331d0c6dee.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -

    <%= link_to l(:label_role_plural), roles_path %> » <%=l(:label_permissions_report)%>

    - -<%= form_tag(permissions_roles_path, :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? %> - - - <% @roles.each do |role| %> - - <% end %> - - <% 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_') %> - <%= h(role.name) %>
    - <%= 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d87bb15034cb1df578fd9d349f69712ac9695482.svn-base --- a/.svn/pristine/d8/d87bb15034cb1df578fd9d349f69712ac9695482.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,749 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::GanttHelperTest < ActionView::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :journals, :journal_details, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :versions, - :groups_users - - include ApplicationHelper - include ProjectsHelper - include IssuesHelper - include ERB::Util - - def setup - setup_with_controller - User.current = User.find(1) - end - - def today - @today ||= Date.today - 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.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)) - 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!(: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!(: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!(: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 => (today + 7), :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 => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - end - - context "project" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.project-name a", /#{@project.name}/ - end - - should "have an indent of 4" do - @output_buffer = @gantt.subjects - assert_select "div.project-name[style*=left:4px]" - end - end - - context "version" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.version-name a", /#{@version.name}/ - end - - should "be indented 24 (one level)" do - @output_buffer = @gantt.subjects - assert_select "div.version-name[style*=left:24px]" - end - - context "without assigned issues" do - setup do - @version = Version.generate!(:effective_date => (today + 14), - :sharing => 'none', - :name => 'empty_version') - @project.versions << @version - end - - should "not be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0 - end - end - end - - context "issue" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.issue-subject", /#{@issue.subject}/ - end - - should "be indented 44 (two levels)" do - @output_buffer = @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.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 => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - end - - should "be rendered" do - @output_buffer = @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 => (today - 1), - :due_date => (today + 2)) - ) - @child2 = Issue.generate!( - attrs.merge(:subject => 'child2', - :parent_issue_id => @issue.id, - :start_date => today, - :due_date => (today + 7)) - ) - @grandchild = Issue.generate!( - attrs.merge(:subject => 'grandchild', - :parent_issue_id => @child1.id, - :start_date => (today - 1), - :due_date => (today + 2)) - ) - end - - should "indent subtasks" do - @output_buffer = @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/, @output_buffer - 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 => (today + 7)) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - @output_buffer = @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 - @output_buffer = @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 - @output_buffer = @gantt.subject_for_project(@project, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the project name" do - @output_buffer = @gantt.subject_for_project(@project, {:format => :html}) - assert_select 'div', :text => /#{@project.name}/ - end - - should "include a link to the project" do - @output_buffer = @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 => (today - 1)) - assert @project.reload.overdue?, "Need an overdue project for this test" - @output_buffer = @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 => (today - 1)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should_eventually "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=left:28px]", true, @output_buffer - end - - should_eventually "be the total delayed width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should_eventually "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=left:28px]", true, @output_buffer - end - - should_eventually "Be the total done width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=width:18px]", true, @output_buffer - 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', today) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting", false, @output_buffer - end - - should "appear at the starting point" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting[style*=left:28px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending", false, @output_buffer - end - - should "appear at the end of the date range" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending[style*=left:88px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should "show the project name" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should_eventually "show the percent complete" do - @output_buffer = @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 => (today - 1)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#subject_for_version", - :tracker => @tracker, - :project => @project, - :start_date => today) - - end - - context ":html format" do - should "add an absolute positioned div" do - @output_buffer = @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 - @output_buffer = @gantt.subject_for_version(@version, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the version name" do - @output_buffer = @gantt.subject_for_version(@version, {:format => :html}) - assert_select 'div', :text => /#{@version.name}/ - end - - should "include a link to the version" do - @output_buffer = @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" - @output_buffer = @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" - @output_buffer = @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 => (today + 7)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=left:28px]", true, @output_buffer - end - - should "be the total delayed width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=left:28px]", true, @output_buffer - end - - should "be the total done width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=width:16px]", true, @output_buffer - 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', today) - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting", false - end - - should "appear at the starting point" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting[style*=left:28px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @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 - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.ending[style*=left:88px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the version name" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the percent complete" do - @output_buffer = @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 => (today - 3), - :due_date => (today - 1)) - @project.issues << @issue - end - - context ":html format" do - should "add an absolute positioned div" do - @output_buffer = @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 - @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the issue subject" do - @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html}) - assert_select 'div', :text => /#{@issue.subject}/ - end - - should "include a link to the issue" do - @output_buffer = @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" - @output_buffer = @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 => (today + 7)) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - @project.issues << @issue - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=left:28px]", true, @output_buffer - end - - should "be the total delayed width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:28px]", true, @output_buffer - end - - should "be the total done width of the issue" do - @output_buffer = @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, @output_buffer - end - - should "not be the total done width if the chart starts after issue start date" do - create_gantt(@project, :date_from => (today - 5)) - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:0px]", true, @output_buffer - assert_select "div.task_done[style*=width:8px]", true, @output_buffer - end - - context "for completed issue" do - setup do - @issue.done_ratio = 100 - end - - should "be the total width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:58px]", true, @output_buffer - end - - should "be the total width of the issue with due_date=start_date" do - @issue.due_date = @issue.start_date - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:2px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", true, @output_buffer - end - - should "show the issue status" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /#{@issue.status.name}/ - end - - should "show the percent complete" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /30%/ - end - end - end - - should "have an issue tooltip" do - @output_buffer = @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d881dab0edca1e23ee9c92bc993d4b8b328df023.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d8/d881dab0edca1e23ee9c92bc993d4b8b328df023.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,88 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d8ed23d57cbf25c5697f0046250deb47d4d986f0.svn-base --- a/.svn/pristine/d8/d8ed23d57cbf25c5697f0046250deb47d4d986f0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -<%= error_messages_for 'tracker' %> - -
    -
    - -

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

    -

    <%= f.check_box :is_in_roadmap %>

    - -

    - - <% Tracker::CORE_FIELDS.each do |field| %> - - <% end %> -

    -<%= hidden_field_tag 'tracker[core_fields][]', '' %> - -<% 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) %> -<%= render_project_nested_lists(@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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d8/d8f473239991f23a64f1a30126c5713289c61cbe.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d8/d8f473239991f23a64f1a30126c5713289c61cbe.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,240 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 RoutingTimelogsTest < ActionController::IntegrationTest + def test_timelogs_global + assert_routing( + { :method => 'get', :path => "/time_entries" }, + { :controller => 'timelog', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/time_entries.csv" }, + { :controller => 'timelog', :action => 'index', :format => 'csv' } + ) + assert_routing( + { :method => 'get', :path => "/time_entries.atom" }, + { :controller => 'timelog', :action => 'index', :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/time_entries/new" }, + { :controller => 'timelog', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/time_entries/22/edit" }, + { :controller => 'timelog', :action => 'edit', :id => '22' } + ) + assert_routing( + { :method => 'post', :path => "/time_entries" }, + { :controller => 'timelog', :action => 'create' } + ) + assert_routing( + { :method => 'put', :path => "/time_entries/22" }, + { :controller => 'timelog', :action => 'update', :id => '22' } + ) + assert_routing( + { :method => 'delete', :path => "/time_entries/55" }, + { :controller => 'timelog', :action => 'destroy', :id => '55' } + ) + end + + def test_timelogs_scoped_under_project + assert_routing( + { :method => 'get', :path => "/projects/567/time_entries" }, + { :controller => 'timelog', :action => 'index', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/time_entries.csv" }, + { :controller => 'timelog', :action => 'index', :project_id => '567', + :format => 'csv' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/time_entries.atom" }, + { :controller => 'timelog', :action => 'index', :project_id => '567', + :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/time_entries/new" }, + { :controller => 'timelog', :action => 'new', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/time_entries/22/edit" }, + { :controller => 'timelog', :action => 'edit', + :id => '22', :project_id => '567' } + ) + assert_routing( + { :method => 'post', :path => "/projects/567/time_entries" }, + { :controller => 'timelog', :action => 'create', + :project_id => '567' } + ) + assert_routing( + { :method => 'put', :path => "/projects/567/time_entries/22" }, + { :controller => 'timelog', :action => 'update', + :id => '22', :project_id => '567' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/567/time_entries/55" }, + { :controller => 'timelog', :action => 'destroy', + :id => '55', :project_id => '567' } + ) + end + + def test_timelogs_scoped_under_issues + assert_routing( + { :method => 'get', :path => "/issues/234/time_entries" }, + { :controller => 'timelog', :action => 'index', :issue_id => '234' } + ) + assert_routing( + { :method => 'get', :path => "/issues/234/time_entries.csv" }, + { :controller => 'timelog', :action => 'index', :issue_id => '234', + :format => 'csv' } + ) + assert_routing( + { :method => 'get', :path => "/issues/234/time_entries.atom" }, + { :controller => 'timelog', :action => 'index', :issue_id => '234', + :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/issues/234/time_entries/new" }, + { :controller => 'timelog', :action => 'new', :issue_id => '234' } + ) + assert_routing( + { :method => 'get', :path => "/issues/234/time_entries/22/edit" }, + { :controller => 'timelog', :action => 'edit', :id => '22', + :issue_id => '234' } + ) + assert_routing( + { :method => 'post', :path => "/issues/234/time_entries" }, + { :controller => 'timelog', :action => 'create', :issue_id => '234' } + ) + assert_routing( + { :method => 'put', :path => "/issues/234/time_entries/22" }, + { :controller => 'timelog', :action => 'update', :id => '22', + :issue_id => '234' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/234/time_entries/55" }, + { :controller => 'timelog', :action => 'destroy', :id => '55', + :issue_id => '234' } + ) + end + + def test_timelogs_scoped_under_project_and_issues + assert_routing( + { :method => 'get', + :path => "/projects/ecookbook/issues/234/time_entries" }, + { :controller => 'timelog', :action => 'index', + :issue_id => '234', :project_id => 'ecookbook' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/ecookbook/issues/234/time_entries.csv" }, + { :controller => 'timelog', :action => 'index', + :issue_id => '234', :project_id => 'ecookbook', :format => 'csv' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/ecookbook/issues/234/time_entries.atom" }, + { :controller => 'timelog', :action => 'index', + :issue_id => '234', :project_id => 'ecookbook', :format => 'atom' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/ecookbook/issues/234/time_entries/new" }, + { :controller => 'timelog', :action => 'new', + :issue_id => '234', :project_id => 'ecookbook' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/ecookbook/issues/234/time_entries/22/edit" }, + { :controller => 'timelog', :action => 'edit', :id => '22', + :issue_id => '234', :project_id => 'ecookbook' } + ) + assert_routing( + { :method => 'post', + :path => "/projects/ecookbook/issues/234/time_entries" }, + { :controller => 'timelog', :action => 'create', + :issue_id => '234', :project_id => 'ecookbook' } + ) + assert_routing( + { :method => 'put', + :path => "/projects/ecookbook/issues/234/time_entries/22" }, + { :controller => 'timelog', :action => 'update', :id => '22', + :issue_id => '234', :project_id => 'ecookbook' } + ) + assert_routing( + { :method => 'delete', + :path => "/projects/ecookbook/issues/234/time_entries/55" }, + { :controller => 'timelog', :action => 'destroy', :id => '55', + :issue_id => '234', :project_id => 'ecookbook' } + ) + end + + def test_timelogs_report + assert_routing( + { :method => 'get', + :path => "/time_entries/report" }, + { :controller => 'timelog', :action => 'report' } + ) + assert_routing( + { :method => 'get', + :path => "/time_entries/report.csv" }, + { :controller => 'timelog', :action => 'report', :format => 'csv' } + ) + assert_routing( + { :method => 'get', + :path => "/issues/234/time_entries/report" }, + { :controller => 'timelog', :action => 'report', :issue_id => '234' } + ) + assert_routing( + { :method => 'get', + :path => "/issues/234/time_entries/report.csv" }, + { :controller => 'timelog', :action => 'report', :issue_id => '234', + :format => 'csv' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/567/time_entries/report" }, + { :controller => 'timelog', :action => 'report', :project_id => '567' } + ) + assert_routing( + { :method => 'get', + :path => "/projects/567/time_entries/report.csv" }, + { :controller => 'timelog', :action => 'report', :project_id => '567', + :format => 'csv' } + ) + end + + def test_timelogs_bulk_edit + assert_routing( + { :method => 'delete', + :path => "/time_entries/destroy" }, + { :controller => 'timelog', :action => 'destroy' } + ) + assert_routing( + { :method => 'post', + :path => "/time_entries/bulk_update" }, + { :controller => 'timelog', :action => 'bulk_update' } + ) + assert_routing( + { :method => 'get', + :path => "/time_entries/bulk_edit" }, + { :controller => 'timelog', :action => 'bulk_edit' } + ) + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d913b426f0ac47a8c67a615cc644671eedcdad09.svn-base --- a/.svn/pristine/d9/d913b426f0ac47a8c67a615cc644671eedcdad09.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 - def self.call(template) - "Redmine::Views::Builders.for(params[:format], request, response) do |api|; #{template.source}; self.output_buffer = api.output; end" - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d91ecb3f88fd014f5736d4fb398c7e45250670a7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d9/d91ecb3f88fd014f5736d4fb398c7e45250670a7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,210 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + after_create :create_journal_after_create + after_destroy :create_journal_after_delete + + 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 + + def create_journal_after_create + journal = issue_from.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_from).to_s, + :value => issue_to.id) + journal.save + journal = issue_to.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_to).to_s, + :value => issue_from.id) + journal.save + end + + def create_journal_after_delete + journal = issue_from.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_from).to_s, + :old_value => issue_to.id) + journal.save + journal = issue_to.init_journal(User.current) + journal.details << JournalDetail.new(:property => 'relation', + :prop_key => label_for(issue_to).to_s, + :old_value => issue_from.id) + journal.save + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d976ee9c65849b907d760e34f50d66028d8e5723.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d9/d976ee9c65849b907d760e34f50d66028d8e5723.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,448 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'cgi' +require 'redmine/scm/adapters' + +if RUBY_VERSION < '1.9' + require 'iconv' +end + +module Redmine + module Scm + module Adapters + class AbstractAdapter #:nodoc: + + # raised if scm command exited with error, e.g. unknown revision. + class ScmCommandAborted < CommandFailed; end + + class << self + def client_command + "" + end + + def shell_quote_command + if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java' + client_command + else + shell_quote(client_command) + end + end + + # Returns the version of the scm client + # Eg: [1, 5, 0] or [] if unknown + def client_version + [] + end + + # Returns the version string of the scm client + # Eg: '1.5.0' or 'Unknown version' if unknown + def client_version_string + v = client_version || 'Unknown version' + v.is_a?(Array) ? v.join('.') : v.to_s + end + + # Returns true if the current client version is above + # or equals the given one + # If option is :unknown is set to true, it will return + # true if the client version is unknown + def client_version_above?(v, options={}) + ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) + end + + def client_available + true + end + + def shell_quote(str) + if Redmine::Platform.mswin? + '"' + str.gsub(/"/, '\\"') + '"' + else + "'" + str.gsub(/'/, "'\"'\"'") + "'" + end + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @url = url + @login = login if login && !login.empty? + @password = (password || "") if @login + @root_url = root_url.blank? ? retrieve_root_url : root_url + end + + def adapter_name + 'Abstract' + end + + def supports_cat? + true + end + + def supports_annotate? + respond_to?('annotate') + end + + def root_url + @root_url + end + + def url + @url + end + + def path_encoding + nil + end + + # get info about the svn repository + def info + return nil + end + + # Returns the entry identified by path and revision identifier + # or nil if entry doesn't exist in the repository + 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) + es ? es.detect {|e| e.name == search_name} : nil + end + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil, options={}) + return nil + end + + def branches + return nil + end + + def tags + return nil + end + + def default_branch + return nil + end + + def properties(path, identifier=nil) + return nil + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + return nil + end + + def diff(path, identifier_from, identifier_to=nil) + return nil + end + + def cat(path, identifier=nil) + return nil + end + + def with_leading_slash(path) + path ||= '' + (path[0,1]!="/") ? "/#{path}" : path + end + + def with_trailling_slash(path) + path ||= '' + (path[-1,1] == "/") ? path : "#{path}/" + end + + def without_leading_slash(path) + path ||= '' + path.gsub(%r{^/+}, '') + end + + def without_trailling_slash(path) + path ||= '' + (path[-1,1] == "/") ? path[0..-2] : path + end + + def shell_quote(str) + self.class.shell_quote(str) + end + + private + def retrieve_root_url + info = self.info + info ? info.root_url : nil + end + + def target(path, sq=true) + path ||= '' + base = path.match(/^\//) ? root_url : url + str = "#{base}/#{path}".gsub(/[?<>\*]/, '') + if sq + str = shell_quote(str) + end + str + end + + def logger + self.class.logger + end + + def shellout(cmd, options = {}, &block) + self.class.shellout(cmd, options, &block) + end + + def self.logger + 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)}" + # Capture stderr in a log file + if stderr_log_file + cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}" + end + end + begin + mode = "r+" + IO.popen(cmd, mode) do |io| + io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding) + io.close_write unless options[:write_stdin] + block.call(io) if block_given? + end + ## If scm command does not exist, + ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException + ## in production environment. + # rescue Errno::ENOENT => e + rescue Exception => e + msg = strip_credential(e.message) + # The command failed, log it and re-raise + logmsg = "SCM command failed, " + logmsg += "make sure that your SCM command (e.g. svn) is " + logmsg += "in PATH (#{ENV['PATH']})\n" + logmsg += "You can configure your scm commands in config/configuration.yml.\n" + logmsg += "#{strip_credential(cmd)}\n" + logmsg += "with: #{msg}" + logger.error(logmsg) + raise CommandFailed.new(msg) + end + end + + # Hides username/password in a given command + def self.strip_credential(cmd) + q = (Redmine::Platform.mswin? ? '"' : "'") + cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') + end + + def strip_credential(cmd) + self.class.strip_credential(cmd) + end + + def scm_iconv(to, from, str) + return nil if str.nil? + return str if to == from + if str.respond_to?(:force_encoding) + str.force_encoding(from) + begin + str.encode(to) + rescue Exception => err + logger.error("failed to convert from #{from} to #{to}. #{err}") + nil + end + else + begin + Iconv.conv(to, from, str) + rescue Iconv::Failure => err + logger.error("failed to convert from #{from} to #{to}. #{err}") + nil + end + end + end + + def parse_xml(xml) + if RUBY_PLATFORM == 'java' + xml = xml.sub(%r{<\?xml[^>]*\?>}, '') + end + ActiveSupport::XmlMini.parse(xml) + end + end + + class Entries < Array + def sort_by_name + dup.sort! {|x,y| + if x.kind == y.kind + x.name.to_s <=> y.name.to_s + else + x.kind <=> y.kind + end + } + end + + def revisions + revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) + end + end + + class Info + attr_accessor :root_url, :lastrev + def initialize(attributes={}) + self.root_url = attributes[:root_url] if attributes[:root_url] + self.lastrev = attributes[:lastrev] + end + end + + class Entry + attr_accessor :name, :path, :kind, :size, :lastrev, :changeset + + def initialize(attributes={}) + self.name = attributes[:name] if attributes[:name] + self.path = attributes[:path] if attributes[:path] + self.kind = attributes[:kind] if attributes[:kind] + self.size = attributes[:size].to_i if attributes[:size] + self.lastrev = attributes[:lastrev] + end + + def is_file? + 'file' == self.kind + end + + def is_dir? + 'dir' == self.kind + end + + def is_text? + Redmine::MimeType.is_type?('text', name) + end + + def author + if changeset + changeset.author.to_s + elsif lastrev + Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first) + end + end + end + + class Revisions < Array + def latest + sort {|x,y| + unless x.time.nil? or y.time.nil? + x.time <=> y.time + else + 0 + end + }.last + end + end + + class Revision + attr_accessor :scmid, :name, :author, :time, :message, + :paths, :revision, :branch, :identifier, + :parents + + def initialize(attributes={}) + self.identifier = attributes[:identifier] + self.scmid = attributes[:scmid] + self.name = attributes[:name] || self.identifier + self.author = attributes[:author] + self.time = attributes[:time] + self.message = attributes[:message] || "" + self.paths = attributes[:paths] + self.revision = attributes[:revision] + self.branch = attributes[:branch] + self.parents = attributes[:parents] + end + + # Returns the readable identifier. + def format_identifier + self.identifier.to_s + end + + def ==(other) + if other.nil? + false + elsif scmid.present? + scmid == other.scmid + elsif identifier.present? + identifier == other.identifier + elsif revision.present? + revision == other.revision + end + end + end + + class Annotate + attr_reader :lines, :revisions + + def initialize + @lines = [] + @revisions = [] + end + + def add_line(line, revision) + @lines << line + @revisions << revision + end + + def content + content = lines.join("\n") + end + + def empty? + lines.empty? + end + end + + class Branch < String + attr_accessor :revision, :scmid + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d97df74b2eafb29ca7f93b4689e578e0c8cdb00e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d9/d97df74b2eafb29ca7f93b4689e578e0c8cdb00e.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,54 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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, :users, :groups_users + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d98580268108411b4971461a40dedaf1dca1cebb.svn-base --- a/.svn/pristine/d9/d98580268108411b4971461a40dedaf1dca1cebb.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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, - :workflows, - :versions - - def setup - end - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d9b67bdb1cd5b0fd18dbd383da288aca840b669d.svn-base --- a/.svn/pristine/d9/d9b67bdb1cd5b0fd18dbd383da288aca840b669d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class SysController < ActionController::Base - before_filter :check_enabled - - def projects - p = Project.active.has_module(:repository).find( - :all, - :include => :repository, - :order => "#{Project.table_name}.identifier" - ) - # extra_info attribute from repository breaks activeresource client - render :xml => p.to_xml( - :only => [:id, :identifier, :name, :is_public, :status], - :include => {:repository => {:only => [:id, :url]}} - ) - end - - def create_project_repository - project = Project.find(params[:id]) - if project.repository - render :nothing => true, :status => 409 - else - logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}." - repository = Repository.factory(params[:vendor], params[:repository]) - repository.project = project - if repository.save - render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201 - else - render :nothing => true, :status => 422 - end - end - end - - def fetch_changesets - projects = [] - scope = Project.active.has_module(:repository) - if params[:id] - project = nil - if params[:id].to_s =~ /^\d*$/ - project = scope.find(params[:id]) - else - project = scope.find_by_identifier(params[:id]) - end - raise ActiveRecord::RecordNotFound unless project - projects << project - else - projects = scope.all - end - projects.each do |project| - project.repositories.each do |repository| - repository.fetch_changesets - end - end - render :nothing => true, :status => 200 - rescue ActiveRecord::RecordNotFound - render :nothing => true, :status => 404 - end - - protected - - def check_enabled - User.current = nil - unless Setting.sys_api_enabled? && params[:key].to_s == Setting.sys_api_key - render :text => 'Access denied. Repository management WS is disabled or key is invalid.', :status => 403 - return false - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/d9/d9d065a525156abf43bd2664f417d23e592967f5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/d9/d9d065a525156abf43bd2664f417d23e592967f5.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,74 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/da05fa62a0900a109d64049958a7a34cf7ed9f81.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/da/da05fa62a0900a109d64049958a7a34cf7ed9f81.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,663 @@ +# -*- coding: utf-8 -*- +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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_my_spent_time + @request.session[:user_id] = 2 + get :index, :user_id => 'me' + assert_response :success + assert_template 'index' + assert assigns(:entries).all? {|entry| entry.user_id == 2} + 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_with_display_subprojects_issues_to_false_should_not_include_subproject_entries + entry = TimeEntry.generate!(:project => Project.find(3)) + + with_settings :display_subprojects_issues => '0' do + get :index, :project_id => 'ecookbook' + assert_response :success + assert_template 'index' + assert_not_include entry, assigns(:entries) + end + end + + def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries + entry = TimeEntry.generate!(:project => Project.find(3)) + + with_settings :display_subprojects_issues => '0' do + get :index, :project_id => 'ecookbook', :subproject_id => 3 + assert_response :success + assert_template 'index' + assert_include entry, assigns(:entries) + end + 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_with_time_entry_custom_field_column + field = TimeEntryCustomField.generate!(:field_format => 'string') + entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'}) + field_name = "cf_#{field.id}" + + get :index, :c => ["hours", field_name] + assert_response :success + assert_include field_name.to_sym, assigns(:query).column_names + assert_select "td.#{field_name}", :text => 'CF Value' + end + + def test_index_with_time_entry_custom_field_sorting + field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field') + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'}) + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'}) + TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'}) + field_name = "cf_#{field.id}" + + get :index, :c => ["hours", field_name], :sort => field_name + assert_response :success + assert_include field_name.to_sym, assigns(:query).column_names + assert_select "th a.sort", :text => 'String Field' + + # Make sure that values are properly sorted + values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact + assert_equal 3, values.size + assert_equal values.sort, values + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/da0cd915314f46c50c5c7c46f0225abb1b6aeea8.svn-base --- a/.svn/pristine/da/da0cd915314f46c50c5c7c46f0225abb1b6aeea8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= principals_check_box_tags 'user_ids[]', @users %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/da77bc020b3601bbe557cb2b9152c1bf47512dd3.svn-base --- a/.svn/pristine/da/da77bc020b3601bbe557cb2b9152c1bf47512dd3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -

    <%=h @issue.tracker %> #<%= @issue.id %>

    -

    <%= authoring @journal.created_on, @journal.user, :label => :label_updated_time_by %>

    - -
    -<%= simple_format_without_paragraph @diff.to_html %> -
    - -

    <%= link_to l(:button_back), issue_path(@issue), :onclick => 'history.back(); return false;' %>

    - -<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/da90c32e29d880fb86d4a92a29e832113b99a3f6.svn-base --- a/.svn/pristine/da/da90c32e29d880fb86d4a92a29e832113b99a3f6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 RoutingWorkflowsTest < ActionController::IntegrationTest - def test_workflows - assert_routing( - { :method => 'get', :path => "/workflows" }, - { :controller => 'workflows', :action => 'index' } - ) - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/workflows/edit" }, - { :controller => 'workflows', :action => 'edit' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/workflows/permissions" }, - { :controller => 'workflows', :action => 'permissions' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/workflows/copy" }, - { :controller => 'workflows', :action => 'copy' } - ) - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/daba4fcb45f422512e01be77f3b55b7ccc6f33ba.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/da/daba4fcb45f422512e01be77f3b55b7ccc6f33ba.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,283 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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_update_keywords_array.map {|r| r['keywords']}.flatten.compact + + 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].to_s.downcase, 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 + # Don't update issues or log time when importing old commits + unless repository.created_on && committed_on && committed_on < repository.created_on + fix_issue(issue, action) if fix_keywords.include?(action) + log_time(issue, hours) if hours && Setting.commit_logtime_enabled? + end + 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) + repo = "" + if repository && repository.identifier.present? + repo = "#{repository.identifier}|" + end + tag = if scmid? + "commit:#{repo}#{scmid}" + else + "#{repo}r#{revision}" + 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 + + # Updates the +issue+ according to +action+ + def fix_issue(issue, action) + # 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))) + rule = Setting.commit_update_keywords_array.detect do |rule| + rule['keywords'].include?(action) && + (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s) + end + if rule + issue.assign_attributes rule.slice(*Issue.attribute_names) + end + Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, + { :changeset => self, :issue => issue, :action => action }) + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/daf4fabdd56445c8d4501b8329ee068b8165fa5c.svn-base --- a/.svn/pristine/da/daf4fabdd56445c8d4501b8329ee068b8165fa5c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1090 +0,0 @@ -# Redmine EU language -# Author: Ales Zabala Alava (Shagi), -# 2010-01-25 -# Distributed under the same terms as the Redmine itself. -eu: - 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: [Igandea, Astelehena, Asteartea, Asteazkena, Osteguna, Ostirala, Larunbata] - abbr_day_names: [Ig., Al., Ar., Az., Og., Or., La.] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Urtarrila, Otsaila, Martxoa, Apirila, Maiatza, Ekaina, Uztaila, Abuztua, Iraila, Urria, Azaroa, Abendua] - abbr_month_names: [~, Urt, Ots, Mar, Api, Mai, Eka, Uzt, Abu, Ira, Urr, Aza, Abe] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y/%m/%d %H:%M" - time: "%H:%M" - short: "%b %d %H:%M" - long: "%Y %B %d %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "minutu erdi" - less_than_x_seconds: - one: "segundu bat baino gutxiago" - other: "%{count} segundu baino gutxiago" - x_seconds: - one: "segundu 1" - other: "%{count} segundu" - less_than_x_minutes: - one: "minutu bat baino gutxiago" - other: "%{count} minutu baino gutxiago" - x_minutes: - one: "minutu 1" - other: "%{count} minutu" - about_x_hours: - one: "ordu 1 inguru" - other: "%{count} ordu inguru" - x_hours: - one: "ordu 1" - other: "%{count} ordu" - x_days: - one: "egun 1" - other: "%{count} egun" - about_x_months: - one: "hilabete 1 inguru" - other: "%{count} hilabete inguru" - x_months: - one: "hilabete 1" - other: "%{count} hilabete" - about_x_years: - one: "urte 1 inguru" - other: "%{count} urte inguru" - over_x_years: - one: "urte 1 baino gehiago" - other: "%{count} urte baino gehiago" - almost_x_years: - one: "ia urte 1" - other: "ia %{count} urte" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 3 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Byte" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "eta" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "Errore batek %{model} hau godetzea galarazi du." - other: "%{count} errorek %{model} hau gordetzea galarazi dute." - messages: - inclusion: "ez dago zerrendan" - exclusion: "erreserbatuta dago" - invalid: "baliogabea da" - confirmation: "ez du berrespenarekin bat egiten" - accepted: "onartu behar da" - empty: "ezin da hutsik egon" - blank: "ezin da hutsik egon" - too_long: "luzeegia da (maximoa %{count} karaktere dira)" - too_short: "laburregia da (minimoa %{count} karaktere dira)" - wrong_length: "luzera ezegokia da (%{count} karakter izan beharko litzake)" - taken: "dagoeneko hartuta dago" - not_a_number: "ez da zenbaki bat" - not_a_date: "ez da baliozko data" - greater_than: "%{count} baino handiagoa izan behar du" - greater_than_or_equal_to: "%{count} edo handiagoa izan behar du" - equal_to: "%{count} izan behar du" - less_than: "%{count} baino gutxiago izan behar du" - less_than_or_equal_to: "%{count} edo gutxiago izan behar du" - odd: "bakoitia izan behar du" - even: "bikoitia izan behar du" - greater_than_start_date: "hasiera data baino handiagoa izan behar du" - not_same_project: "ez dago proiektu berdinean" - circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" - cant_link_an_issue_with_a_descendant: "Zeregin bat ezin da bere azpiataza batekin estekatu." - - actionview_instancetag_blank_option: Hautatu mesedez - - general_text_No: 'Ez' - general_text_Yes: 'Bai' - general_text_no: 'ez' - general_text_yes: 'bai' - general_lang_name: 'Euskara' - 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: Kontua ongi eguneratu da. - notice_account_invalid_creditentials: Erabiltzaile edo pasahitz ezegokia - notice_account_password_updated: Pasahitza ongi eguneratu da. - notice_account_wrong_password: Pasahitz ezegokia. - notice_account_register_done: Kontua ongi sortu da. Kontua gaitzeko klikatu epostan adierazi zaizun estekan. - notice_account_unknown_email: Erabiltzaile ezezaguna. - notice_can_t_change_password: Kontu honek kanpoko autentikazio bat erabiltzen du. Ezinezkoa da pasahitza aldatzea. - notice_account_lost_email_sent: Pasahitz berria aukeratzeko jarraibideak dituen eposta bat bidali zaizu. - notice_account_activated: Zure kontua gaituta dago. Orain saioa has dezakezu - notice_successful_create: Sortze arrakastatsua. - notice_successful_update: Eguneratze arrakastatsua. - notice_successful_delete: Ezabaketa arrakastatsua. - notice_successful_connection: Konexio arrakastatsua. - notice_file_not_found: Atzitu nahi duzun orria ez da exisitzen edo ezabatua izan da. - notice_locking_conflict: Beste erabiltzaile batek datuak eguneratu ditu. - notice_not_authorized: Ez duzu orri hau atzitzeko baimenik. - notice_email_sent: "%{value} helbidera eposta bat bidali da" - notice_email_error: "Errorea eposta bidaltzean (%{value})" - notice_feeds_access_key_reseted: Zure RSS atzipen giltza berrezarri da. - notice_api_access_key_reseted: Zure API atzipen giltza berrezarri da. - notice_failed_to_save_issues: "Hautatutako %{total} zereginetatik %{count} ezin izan dira konpondu: %{ids}." - notice_no_issue_selected: "Ez da zereginik hautatu! Mesedez, editatu nahi dituzun arazoak markatu." - notice_account_pending: "Zure kontua sortu da, orain kudeatzailearen onarpenaren zain dago." - notice_default_data_loaded: Lehenetsitako konfigurazioa ongi kargatu da. - notice_unable_delete_version: Ezin da bertsioa ezabatu. - notice_issue_done_ratios_updated: Burututako zereginen erlazioa eguneratu da. - - error_can_t_load_default_data: "Ezin izan da lehenetsitako konfigurazioa kargatu: %{value}" - error_scm_not_found: "Sarrera edo berrikuspena ez da biltegian topatu." - error_scm_command_failed: "Errorea gertatu da biltegia atzitzean: %{value}" - error_scm_annotate: "Sarrera ez da existitzen edo ezin da anotatu." - error_issue_not_found_in_project: 'Zeregina ez da topatu edo ez da proiektu honetakoa' - error_no_tracker_in_project: 'Proiektu honek ez du aztarnaririk esleituta. Mesedez egiaztatu Proiektuaren ezarpenak.' - error_no_default_issue_status: 'Zereginek ez dute lehenetsitako egoerarik. Mesedez egiaztatu zure konfigurazioa ("Kudeaketa -> Arazoen egoerak" atalera joan).' - error_can_not_reopen_issue_on_closed_version: 'Itxitako bertsio batera esleitutako zereginak ezin dira berrireki' - error_can_not_archive_project: Proiektu hau ezin da artxibatu - error_issue_done_ratios_not_updated: "Burututako zereginen erlazioa ez da eguneratu." - error_workflow_copy_source: 'Mesedez hautatu iturburuko aztarnari edo rola' - error_workflow_copy_target: 'Mesedez hautatu helburuko aztarnari(ak) edo rola(k)' - - warning_attachments_not_saved: "%{count} fitxategi ezin izan d(ir)a gorde." - - mail_subject_lost_password: "Zure %{value} pasahitza" - mail_body_lost_password: 'Zure pasahitza aldatzeko hurrengo estekan klikatu:' - mail_subject_register: "Zure %{value} kontuaren gaitzea" - mail_body_register: 'Zure kontua gaitzeko hurrengo estekan klikatu:' - mail_body_account_information_external: "Zure %{value} kontua erabil dezakezu saioa hasteko." - mail_body_account_information: Zure kontuaren informazioa - mail_subject_account_activation_request: "%{value} kontu gaitzeko eskaera" - mail_body_account_activation_request: "Erabiltzaile berri bat (%{value}) erregistratu da. Kontua zure onarpenaren zain dago:" - mail_subject_reminder: "%{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a" - mail_body_reminder: "Zuri esleituta dauden %{count} arazo hurrengo %{days} egunetan amaitzen d(ir)a:" - mail_subject_wiki_content_added: "'%{id}' wiki orria gehitu da" - mail_body_wiki_content_added: "%{author}-(e)k '%{id}' wiki orria gehitu du." - mail_subject_wiki_content_updated: "'%{id}' wiki orria eguneratu da" - mail_body_wiki_content_updated: "%{author}-(e)k '%{id}' wiki orria eguneratu du." - - - field_name: Izena - field_description: Deskribapena - field_summary: Laburpena - field_is_required: Beharrezkoa - field_firstname: Izena - field_lastname: Abizenak - field_mail: Eposta - field_filename: Fitxategia - field_filesize: Tamaina - field_downloads: Deskargak - field_author: Egilea - field_created_on: Sortuta - field_updated_on: Eguneratuta - field_field_format: Formatua - field_is_for_all: Proiektu guztietarako - field_possible_values: Balio posibleak - field_regexp: Expresio erregularra - field_min_length: Luzera minimoa - field_max_length: Luzera maxioma - field_value: Balioa - field_category: Kategoria - field_title: Izenburua - field_project: Proiektua - field_issue: Zeregina - field_status: Egoera - field_notes: Oharrak - field_is_closed: Itxitako arazoa - field_is_default: Lehenetsitako balioa - field_tracker: Aztarnaria - field_subject: Gaia - field_due_date: Amaiera data - field_assigned_to: Esleituta - field_priority: Lehentasuna - field_fixed_version: Helburuko bertsioa - field_user: Erabiltzilea - field_role: Rola - field_homepage: Orri nagusia - field_is_public: Publikoa - field_parent: "Honen azpiproiektua:" - field_is_in_roadmap: Arazoak ibilbide-mapan erakutsi - field_login: Erabiltzaile izena - field_mail_notification: Eposta jakinarazpenak - field_admin: Kudeatzailea - field_last_login_on: Azken konexioa - field_language: Hizkuntza - field_effective_date: Data - field_password: Pasahitza - field_new_password: Pasahitz berria - field_password_confirmation: Berrespena - field_version: Bertsioa - field_type: Mota - field_host: Ostalaria - field_port: Portua - field_account: Kontua - field_base_dn: Base DN - field_attr_login: Erabiltzaile atributua - field_attr_firstname: Izena atributua - field_attr_lastname: Abizenak atributua - field_attr_mail: Eposta atributua - field_onthefly: Zuzeneko erabiltzaile sorrera - field_start_date: Hasiera - field_done_ratio: Egindako % - field_auth_source: Autentikazio modua - field_hide_mail: Nire eposta helbidea ezkutatu - field_comments: Iruzkina - field_url: URL - field_start_page: Hasierako orria - field_subproject: Azpiproiektua - field_hours: Ordu - field_activity: Jarduera - field_spent_on: Data - field_identifier: Identifikatzailea - field_is_filter: Iragazki moduan erabilita - field_issue_to: Erlazionatutako zereginak - field_delay: Atzerapena - field_assignable: Arazoak rol honetara esleitu daitezke - field_redirect_existing_links: Existitzen diren estekak berbideratu - field_estimated_hours: Estimatutako denbora - field_column_names: Zutabeak - field_time_zone: Ordu zonaldea - field_searchable: Bilagarria - field_default_value: Lehenetsitako balioa - field_comments_sorting: Iruzkinak erakutsi - field_parent_title: Orri gurasoa - field_editable: Editagarria - field_watcher: Behatzailea - field_identity_url: OpenID URLa - field_content: Edukia - field_group_by: Emaitzak honegatik taldekatu - field_sharing: Partekatzea - - setting_app_title: Aplikazioaren izenburua - setting_app_subtitle: Aplikazioaren azpizenburua - setting_welcome_text: Ongietorriko testua - setting_default_language: Lehenetsitako hizkuntza - setting_login_required: Autentikazioa derrigorrezkoa - setting_self_registration: Norberak erregistratu - setting_attachment_max_size: Eranskinen tamaina max. - setting_issues_export_limit: Zereginen esportatze limitea - setting_mail_from: Igorlearen eposta helbidea - setting_bcc_recipients: Hartzaileak ezkutuko kopian (bcc) - setting_plain_text_mail: Testu soileko epostak (HTML-rik ez) - setting_host_name: Ostalari izena eta bidea - setting_text_formatting: Testu formatua - setting_wiki_compression: Wikiaren historia konprimitu - setting_feeds_limit: Jarioaren edukiera limitea - setting_default_projects_public: Proiektu berriak defektuz publikoak dira - setting_autofetch_changesets: Commit-ak automatikoki hartu - setting_sys_api_enabled: Biltegien kudeaketarako WS gaitu - setting_commit_ref_keywords: Erreferentzien gako-hitzak - setting_commit_fix_keywords: Konpontze gako-hitzak - setting_autologin: Saioa automatikoki hasi - setting_date_format: Data formatua - setting_time_format: Ordu formatua - setting_cross_project_issue_relations: Zereginak proiektuen artean erlazionatzea baimendu - setting_issue_list_default_columns: Zereginen zerrendan defektuz ikusten diren zutabeak - setting_emails_footer: Eposten oina - setting_protocol: Protokoloa - setting_per_page_options: Orriko objektuen aukerak - setting_user_format: Erabiltzaileak erakusteko formatua - setting_activity_days_default: Proiektuen jardueran erakusteko egunak - setting_display_subprojects_issues: Azpiproiektuen zereginak proiektu nagusian erakutsi defektuz - setting_enabled_scm: Gaitutako IKKak - setting_mail_handler_body_delimiters: "Lerro hauteko baten ondoren epostak moztu" - setting_mail_handler_api_enabled: Sarrerako epostentzako WS gaitu - setting_mail_handler_api_key: API giltza - setting_sequential_project_identifiers: Proiektuen identifikadore sekuentzialak sortu - setting_gravatar_enabled: Erabili Gravatar erabiltzaile ikonoak - setting_gravatar_default: Lehenetsitako Gravatar irudia - setting_diff_max_lines_displayed: Erakutsiko diren diff lerro kopuru maximoa - setting_file_max_size_displayed: Barnean erakuzten diren testu fitxategien tamaina maximoa - setting_repository_log_display_limit: Egunkari fitxategian erakutsiko diren berrikuspen kopuru maximoa. - setting_openid: Baimendu OpenID saio hasiera eta erregistatzea - setting_password_min_length: Pasahitzen luzera minimoa - setting_new_project_user_role_id: Proiektu berriak sortzerakoan kudeatzaile ez diren erabiltzaileei esleitutako rola - setting_default_projects_modules: Proiektu berrientzako defektuz gaitutako moduluak - setting_issue_done_ratio: "Zereginen burututako tasa kalkulatzean erabili:" - setting_issue_done_ratio_issue_field: Zeregin eremua erabili - setting_issue_done_ratio_issue_status: Zeregin egoera erabili - setting_start_of_week: "Egutegiak noiz hasi:" - setting_rest_api_enabled: Gaitu REST web zerbitzua - - permission_add_project: Proiektua sortu - permission_add_subprojects: Azpiproiektuak sortu - permission_edit_project: Proiektua editatu - permission_select_project_modules: Proiektuaren moduluak hautatu - permission_manage_members: Kideak kudeatu - permission_manage_versions: Bertsioak kudeatu - permission_manage_categories: Arazoen kategoriak kudeatu - permission_view_issues: Zereginak ikusi - permission_add_issues: Zereginak gehitu - permission_edit_issues: Zereginak aldatu - permission_manage_issue_relations: Zereginen erlazioak kudeatu - permission_add_issue_notes: Oharrak gehitu - permission_edit_issue_notes: Oharrak aldatu - permission_edit_own_issue_notes: Nork bere oharrak aldatu - permission_move_issues: Zereginak mugitu - permission_delete_issues: Zereginak ezabatu - permission_manage_public_queries: Galdera publikoak kudeatu - permission_save_queries: Galderak gorde - permission_view_gantt: Gantt grafikoa ikusi - permission_view_calendar: Egutegia ikusi - permission_view_issue_watchers: Behatzaileen zerrenda ikusi - permission_add_issue_watchers: Behatzaileak gehitu - permission_delete_issue_watchers: Behatzaileak ezabatu - permission_log_time: Igarotako denbora erregistratu - permission_view_time_entries: Igarotako denbora ikusi - permission_edit_time_entries: Denbora egunkariak editatu - permission_edit_own_time_entries: Nork bere denbora egunkariak editatu - permission_manage_news: Berriak kudeatu - permission_comment_news: Berrien iruzkinak egin - permission_view_documents: Dokumentuak ikusi - permission_manage_files: Fitxategiak kudeatu - permission_view_files: Fitxategiak ikusi - permission_manage_wiki: Wikia kudeatu - permission_rename_wiki_pages: Wiki orriak berrizendatu - permission_delete_wiki_pages: Wiki orriak ezabatu - permission_view_wiki_pages: Wikia ikusi - permission_view_wiki_edits: Wikiaren historia ikusi - permission_edit_wiki_pages: Wiki orriak editatu - permission_delete_wiki_pages_attachments: Eranskinak ezabatu - permission_protect_wiki_pages: Wiki orriak babestu - permission_manage_repository: Biltegiak kudeatu - permission_browse_repository: Biltegia arakatu - permission_view_changesets: Aldaketak ikusi - permission_commit_access: Commit atzipena - permission_manage_boards: Foroak kudeatu - permission_view_messages: Mezuak ikusi - permission_add_messages: Mezuak bidali - permission_edit_messages: Mezuak aldatu - permission_edit_own_messages: Nork bere mezuak aldatu - permission_delete_messages: Mezuak ezabatu - permission_delete_own_messages: Nork bere mezuak ezabatu - - project_module_issue_tracking: Zereginen jarraipena - project_module_time_tracking: Denbora jarraipena - project_module_news: Berriak - project_module_documents: Dokumentuak - project_module_files: Fitxategiak - project_module_wiki: Wiki - project_module_repository: Biltegia - project_module_boards: Foroak - - label_user: Erabiltzailea - label_user_plural: Erabiltzaileak - label_user_new: Erabiltzaile berria - label_user_anonymous: Ezezaguna - label_project: Proiektua - label_project_new: Proiektu berria - label_project_plural: Proiektuak - label_x_projects: - zero: proiekturik ez - one: proiektu bat - other: "%{count} proiektu" - label_project_all: Proiektu guztiak - label_project_latest: Azken proiektuak - label_issue: Zeregina - label_issue_new: Zeregin berria - label_issue_plural: Zereginak - label_issue_view_all: Zeregin guztiak ikusi - label_issues_by: "Zereginak honengatik: %{value}" - label_issue_added: Zeregina gehituta - label_issue_updated: Zeregina eguneratuta - label_document: Dokumentua - label_document_new: Dokumentu berria - label_document_plural: Dokumentuak - label_document_added: Dokumentua gehituta - label_role: Rola - label_role_plural: Rolak - label_role_new: Rol berria - label_role_and_permissions: Rolak eta baimenak - label_member: Kidea - label_member_new: Kide berria - label_member_plural: Kideak - label_tracker: Aztarnaria - label_tracker_plural: Aztarnariak - label_tracker_new: Aztarnari berria - label_workflow: Lan-fluxua - label_issue_status: Zeregin egoera - label_issue_status_plural: Zeregin egoerak - label_issue_status_new: Egoera berria - label_issue_category: Zeregin kategoria - label_issue_category_plural: Zeregin kategoriak - label_issue_category_new: Kategoria berria - label_custom_field: Eremu pertsonalizatua - label_custom_field_plural: Eremu pertsonalizatuak - label_custom_field_new: Eremu pertsonalizatu berria - label_enumerations: Enumerazioak - label_enumeration_new: Balio berria - label_information: Informazioa - label_information_plural: Informazioa - label_please_login: Saioa hasi mesedez - label_register: Erregistratu - label_login_with_open_id_option: edo OpenID-rekin saioa hasi - label_password_lost: Pasahitza galduta - label_home: Hasiera - label_my_page: Nire orria - label_my_account: Nire kontua - label_my_projects: Nire proiektuak - label_administration: Kudeaketa - label_login: Saioa hasi - label_logout: Saioa bukatu - label_help: Laguntza - label_reported_issues: Berri emandako zereginak - label_assigned_to_me_issues: Niri esleitutako arazoak - label_last_login: Azken konexioa - label_registered_on: Noiz erregistratuta - label_activity: Jarduerak - label_overall_activity: Jarduera guztiak - label_user_activity: "%{value}-(r)en jarduerak" - label_new: Berria - label_logged_as: "Sartutako erabiltzailea:" - label_environment: Ingurune - label_authentication: Autentikazioa - label_auth_source: Autentikazio modua - label_auth_source_new: Autentikazio modu berria - label_auth_source_plural: Autentikazio moduak - label_subproject_plural: Azpiproiektuak - label_subproject_new: Azpiproiektu berria - label_and_its_subprojects: "%{value} eta bere azpiproiektuak" - label_min_max_length: Luzera min - max - label_list: Zerrenda - label_date: Data - label_integer: Osokoa - label_float: Koma higikorrekoa - label_boolean: Boolearra - label_string: Testua - label_text: Testu luzea - label_attribute: Atributua - label_attribute_plural: Atributuak - label_no_data: Ez dago erakusteko daturik - label_change_status: Egoera aldatu - label_history: Historikoa - label_attachment: Fitxategia - label_attachment_new: Fitxategi berria - label_attachment_delete: Fitxategia ezabatu - label_attachment_plural: Fitxategiak - label_file_added: Fitxategia gehituta - label_report: Berri ematea - label_report_plural: Berri emateak - label_news: Berria - label_news_new: Berria gehitu - label_news_plural: Berriak - label_news_latest: Azken berriak - label_news_view_all: Berri guztiak ikusi - label_news_added: Berria gehituta - label_settings: Ezarpenak - label_overview: Gainbegirada - label_version: Bertsioa - label_version_new: Bertsio berria - label_version_plural: Bertsioak - label_close_versions: Burututako bertsioak itxi - label_confirmation: Baieztapena - label_export_to: 'Eskuragarri baita:' - label_read: Irakurri... - label_public_projects: Proiektu publikoak - label_open_issues: irekita - label_open_issues_plural: irekiak - label_closed_issues: itxita - label_closed_issues_plural: itxiak - label_x_open_issues_abbr_on_total: - zero: 0 irekita / %{total} - one: 1 irekita / %{total} - other: "%{count} irekiak / %{total}" - label_x_open_issues_abbr: - zero: 0 irekita - one: 1 irekita - other: "%{count} irekiak" - label_x_closed_issues_abbr: - zero: 0 itxita - one: 1 itxita - other: "%{count} itxiak" - label_total: Guztira - label_permissions: Baimenak - label_current_status: Uneko egoera - label_new_statuses_allowed: Baimendutako egoera berriak - label_all: guztiak - label_none: ezer - label_nobody: inor - label_next: Hurrengoa - label_previous: Aurrekoak - label_used_by: Erabilita - label_details: Xehetasunak - label_add_note: Oharra gehitu - label_per_page: Orriko - label_calendar: Egutegia - label_months_from: hilabete noiztik - label_gantt: Gantt - label_internal: Barnekoa - label_last_changes: "azken %{count} aldaketak" - label_change_view_all: Aldaketa guztiak ikusi - label_personalize_page: Orri hau pertsonalizatu - label_comment: Iruzkin - label_comment_plural: Iruzkinak - label_x_comments: - zero: iruzkinik ez - one: iruzkin 1 - other: "%{count} iruzkin" - label_comment_add: Iruzkina gehitu - label_comment_added: Iruzkina gehituta - label_comment_delete: Iruzkinak ezabatu - label_query: Galdera pertsonalizatua - label_query_plural: Pertsonalizatutako galderak - label_query_new: Galdera berria - label_filter_add: Iragazkia gehitu - label_filter_plural: Iragazkiak - label_equals: da - label_not_equals: ez da - label_in_less_than: baino gutxiagotan - label_in_more_than: baino gehiagotan - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: hauetan - label_today: gaur - label_all_time: denbora guztia - label_yesterday: atzo - label_this_week: aste honetan - label_last_week: pasadan astean - label_last_n_days: "azken %{count} egunetan" - label_this_month: hilabete hau - label_last_month: pasadan hilabetea - label_this_year: urte hau - label_date_range: Data tartea - label_less_than_ago: egun hauek baino gutxiago - label_more_than_ago: egun hauek baino gehiago - label_ago: orain dela - label_contains: dauka - label_not_contains: ez dauka - label_day_plural: egun - label_repository: Biltegia - label_repository_plural: Biltegiak - label_browse: Arakatu - label_branch: Adarra - label_tag: Etiketa - label_revision: Berrikuspena - label_revision_plural: Berrikuspenak - label_revision_id: "%{value} berrikuspen" - label_associated_revisions: Elkartutako berrikuspenak - label_added: gehituta - label_modified: aldatuta - label_copied: kopiatuta - label_renamed: berrizendatuta - label_deleted: ezabatuta - label_latest_revision: Azken berrikuspena - label_latest_revision_plural: Azken berrikuspenak - label_view_revisions: Berrikuspenak ikusi - label_view_all_revisions: Berrikuspen guztiak ikusi - label_max_size: Tamaina maximoa - label_sort_highest: Goraino mugitu - label_sort_higher: Gora mugitu - label_sort_lower: Behera mugitu - label_sort_lowest: Beheraino mugitu - label_roadmap: Ibilbide-mapa - label_roadmap_due_in: "Epea: %{value}" - label_roadmap_overdue: "%{value} berandu" - label_roadmap_no_issues: Ez dago zereginik bertsio honetan - label_search: Bilatu - label_result_plural: Emaitzak - label_all_words: hitz guztiak - label_wiki: Wikia - label_wiki_edit: Wiki edizioa - label_wiki_edit_plural: Wiki edizioak - label_wiki_page: Wiki orria - label_wiki_page_plural: Wiki orriak - label_index_by_title: Izenburuaren araberako indizea - label_index_by_date: Dataren araberako indizea - label_current_version: Uneko bertsioa - label_preview: Aurreikusi - label_feed_plural: Jarioak - label_changes_details: Aldaketa guztien xehetasunak - label_issue_tracking: Zeregin jarraipena - label_spent_time: Igarotako denbora - label_f_hour: "ordu %{value}" - label_f_hour_plural: "%{value} ordu" - label_time_tracking: Denbora jarraipena - label_change_plural: Aldaketak - label_statistics: Estatistikak - label_commits_per_month: Commit-ak hilabeteka - label_commits_per_author: Commit-ak egileka - label_view_diff: Ezberdintasunak ikusi - label_diff_inline: barnean - label_diff_side_by_side: aldez alde - label_options: Aukerak - label_copy_workflow_from: Kopiatu workflow-a hemendik - label_permissions_report: Baimenen txostena - label_watched_issues: Behatutako zereginak - label_related_issues: Erlazionatutako zereginak - label_applied_status: Aplikatutako egoera - label_loading: Kargatzen... - label_relation_new: Erlazio berria - label_relation_delete: Erlazioa ezabatu - label_relates_to: erlazionatuta dago - label_duplicates: bikoizten du - label_duplicated_by: honek bikoiztuta - label_blocks: blokeatzen du - label_blocked_by: honek blokeatuta - label_precedes: aurretik doa - label_follows: jarraitzen du - label_end_to_start: bukaeratik hasierara - label_end_to_end: bukaeratik bukaerara - label_start_to_start: hasieratik hasierhasieratik bukaerara - label_start_to_end: hasieratik bukaerara - label_stay_logged_in: Saioa mantendu - label_disabled: ezgaituta - label_show_completed_versions: Bukatutako bertsioak ikusi - label_me: ni - label_board: Foroa - label_board_new: Foro berria - label_board_plural: Foroak - label_topic_plural: Gaiak - label_message_plural: Mezuak - label_message_last: Azken mezua - label_message_new: Mezu berria - label_message_posted: Mezua gehituta - label_reply_plural: Erantzunak - label_send_information: Erabiltzaileai kontuaren informazioa bidali - label_year: Urtea - label_month: Hilabetea - label_week: Astea - label_date_from: Nork - label_date_to: Nori - label_language_based: Erabiltzailearen hizkuntzaren arabera - label_sort_by: "Ordenazioa: %{value}" - label_send_test_email: Frogako mezua bidali - label_feeds_access_key: RSS atzipen giltza - label_missing_feeds_access_key: RSS atzipen giltza falta da - label_feeds_access_key_created_on: "RSS atzipen giltza orain dela %{value} sortuta" - label_module_plural: Moduluak - label_added_time_by: "%{author}, orain dela %{age} gehituta" - label_updated_time_by: "%{author}, orain dela %{age} eguneratuta" - label_updated_time: "Orain dela %{value} eguneratuta" - label_jump_to_a_project: Joan proiektura... - label_file_plural: Fitxategiak - label_changeset_plural: Aldaketak - label_default_columns: Lehenetsitako zutabeak - label_no_change_option: (Aldaketarik ez) - label_bulk_edit_selected_issues: Hautatutako zereginak batera editatu - label_theme: Itxura - label_default: Lehenetsia - label_search_titles_only: Izenburuetan bakarrik bilatu - label_user_mail_option_all: "Nire proiektu guztietako gertakari guztientzat" - label_user_mail_option_selected: "Hautatutako proiektuetako edozein gertakarientzat..." - label_user_mail_no_self_notified: "Ez dut nik egiten ditudan aldeketen jakinarazpenik jaso nahi" - label_registration_activation_by_email: kontuak epostaz gaitu - label_registration_manual_activation: kontuak eskuz gaitu - label_registration_automatic_activation: kontuak automatikoki gaitu - label_display_per_page: "Orriko: %{value}" - label_age: Adina - label_change_properties: Propietateak aldatu - label_general: Orokorra - label_more: Gehiago - label_scm: IKK - label_plugins: Pluginak - label_ldap_authentication: LDAP autentikazioa - label_downloads_abbr: Desk. - label_optional_description: Aukerako deskribapena - label_add_another_file: Beste fitxategia gehitu - label_preferences: Hobespenak - label_chronological_order: Orden kronologikoan - label_reverse_chronological_order: Alderantzizko orden kronologikoan - label_planning: Planifikazioa - label_incoming_emails: Sarrerako epostak - label_generate_key: Giltza sortu - label_issue_watchers: Behatzaileak - label_example: Adibidea - label_display: Bistaratzea - label_sort: Ordenatu - label_ascending: Gorantz - label_descending: Beherantz - label_date_from_to: "%{start}-tik %{end}-ra" - label_wiki_content_added: Wiki orria gehituta - label_wiki_content_updated: Wiki orria eguneratuta - label_group: Taldea - label_group_plural: Taldeak - label_group_new: Talde berria - label_time_entry_plural: Igarotako denbora - label_version_sharing_none: Ez partekatuta - label_version_sharing_descendants: Azpiproiektuekin - label_version_sharing_hierarchy: Proiektu Hierarkiarekin - label_version_sharing_tree: Proiektu zuhaitzarekin - label_version_sharing_system: Proiektu guztiekin - label_update_issue_done_ratios: Zereginen burututako erlazioa eguneratu - label_copy_source: Iturburua - label_copy_target: Helburua - label_copy_same_as_target: Helburuaren berdina - label_display_used_statuses_only: Aztarnari honetan erabiltzen diren egoerak bakarrik erakutsi - label_api_access_key: API atzipen giltza - label_missing_api_access_key: API atzipen giltza falta da - label_api_access_key_created_on: "API atzipen giltza sortuta orain dela %{value}" - - button_login: Saioa hasi - button_submit: Bidali - button_save: Gorde - button_check_all: Guztiak markatu - button_uncheck_all: Guztiak desmarkatu - button_delete: Ezabatu - button_create: Sortu - button_create_and_continue: Sortu eta jarraitu - button_test: Frogatu - button_edit: Editatu - button_add: Gehitu - button_change: Aldatu - button_apply: Aplikatu - button_clear: Garbitu - button_lock: Blokeatu - button_unlock: Desblokeatu - button_download: Deskargatu - button_list: Zerrenda - button_view: Ikusi - button_move: Mugitu - button_move_and_follow: Mugitu eta jarraitu - button_back: Atzera - button_cancel: Ezeztatu - button_activate: Gahitu - button_sort: Ordenatu - button_log_time: Denbora erregistratu - button_rollback: Itzuli bertsio honetara - button_watch: Behatu - button_unwatch: Behatzen utzi - button_reply: Erantzun - button_archive: Artxibatu - button_unarchive: Desartxibatu - button_reset: Berrezarri - button_rename: Berrizendatu - button_change_password: Pasahitza aldatu - button_copy: Kopiatu - button_copy_and_follow: Kopiatu eta jarraitu - button_annotate: Anotatu - button_update: Eguneratu - button_configure: Konfiguratu - button_quote: Aipatu - button_duplicate: Bikoiztu - button_show: Ikusi - - status_active: gaituta - status_registered: izena emanda - status_locked: blokeatuta - - version_status_open: irekita - version_status_locked: blokeatuta - version_status_closed: itxita - - field_active: Gaituta - - text_select_mail_notifications: Jakinarazpenak zein ekintzetarako bidaliko diren hautatu. - text_regexp_info: adib. ^[A-Z0-9]+$ - text_min_max_length_info: 0k mugarik gabe esan nahi du - text_project_destroy_confirmation: Ziur zaude proiektu hau eta erlazionatutako datu guztiak ezabatu nahi dituzula? - text_subprojects_destroy_warning: "%{value} azpiproiektuak ere ezabatuko dira." - text_workflow_edit: Hautatu rola eta aztarnaria workflow-a editatzeko - text_are_you_sure: Ziur zaude? - text_journal_changed: "%{label} %{old}-(e)tik %{new}-(e)ra aldatuta" - text_journal_set_to: "%{label}-k %{value} balioa hartu du" - text_journal_deleted: "%{label} ezabatuta (%{old})" - text_journal_added: "%{label} %{value} gehituta" - text_tip_issue_begin_day: gaur hasten diren zereginak - text_tip_issue_end_day: gaur bukatzen diren zereginak - text_tip_issue_begin_end_day: gaur hasi eta bukatzen diren zereginak - text_caracters_maximum: "%{count} karaktere gehienez." - text_caracters_minimum: "Gutxienez %{count} karaktereetako luzerakoa izan behar du." - text_length_between: "Luzera %{min} eta %{max} karaktereen artekoa." - text_tracker_no_workflow: Ez da workflow-rik definitu aztarnari honentzako - text_unallowed_characters: Debekatutako karaktereak - text_comma_separated: Balio anitz izan daitezke (komaz banatuta). - text_line_separated: Balio anitz izan daitezke (balio bakoitza lerro batean). - text_issues_ref_in_commit_messages: Commit-en mezuetan zereginak erlazionatu eta konpontzen - text_issue_added: "%{id} zeregina %{author}-(e)k jakinarazi du." - text_issue_updated: "%{id} zeregina %{author}-(e)k eguneratu du." - text_wiki_destroy_confirmation: Ziur zaude wiki hau eta bere eduki guztiak ezabatu nahi dituzula? - text_issue_category_destroy_question: "Zeregin batzuk (%{count}) kategoria honetara esleituta daude. Zer egin nahi duzu?" - text_issue_category_destroy_assignments: Kategoria esleipenak kendu - text_issue_category_reassign_to: Zereginak kategoria honetara esleitu - text_user_mail_option: "Hautatu gabeko proiektuetan, behatzen edo parte hartzen duzun gauzei buruzko jakinarazpenak jasoko dituzu (adib. zu egile zaren edo esleituta dituzun zereginak)." - text_no_configuration_data: "Rolak, aztarnariak, zeregin egoerak eta workflow-ak ez dira oraindik konfiguratu.\nOso gomendagarria de lehenetsitako kkonfigurazioa kargatzea. Kargatu eta gero aldatu ahalko duzu." - text_load_default_configuration: Lehenetsitako konfigurazioa kargatu - text_status_changed_by_changeset: "%{value} aldaketan aplikatuta." - text_issues_destroy_confirmation: 'Ziur zaude hautatutako zeregina(k) ezabatu nahi dituzula?' - text_select_project_modules: 'Hautatu proiektu honetan gaitu behar diren moduluak:' - text_default_administrator_account_changed: Lehenetsitako kudeatzaile kontua aldatuta - text_file_repository_writable: Eranskinen direktorioan idatz daiteke - text_plugin_assets_writable: Pluginen baliabideen direktorioan idatz daiteke - text_rmagick_available: RMagick eskuragarri (aukerazkoa) - text_destroy_time_entries_question: "%{hours} orduei buruz berri eman zen zuk ezabatzera zoazen zereginean. Zer egin nahi duzu?" - text_destroy_time_entries: Ezabatu berri emandako orduak - text_assign_time_entries_to_project: Berri emandako orduak proiektura esleitu - text_reassign_time_entries: 'Berri emandako orduak zeregin honetara esleitu:' - text_user_wrote: "%{value}-(e)k idatzi zuen:" - text_enumeration_destroy_question: "%{count} objetu balio honetara esleituta daude." - text_enumeration_category_reassign_to: 'Beste balio honetara esleitu:' - text_email_delivery_not_configured: "Eposta bidalketa ez dago konfiguratuta eta jakinarazpenak ezgaituta daude.\nKonfiguratu zure SMTP zerbitzaria config/configuration.yml-n eta aplikazioa berrabiarazi hauek gaitzeko." - text_repository_usernames_mapping: "Hautatu edo eguneratu Redmineko erabiltzailea biltegiko egunkarietan topatzen diren erabiltzaile izenekin erlazionatzeko.\nRedmine-n eta biltegian erabiltzaile izen edo eposta berdina duten erabiltzaileak automatikoki erlazionatzen dira." - text_diff_truncated: '... Diff hau moztua izan da erakus daitekeen tamaina maximoa gainditu duelako.' - text_custom_field_possible_values_info: 'Lerro bat balio bakoitzeko' - text_wiki_page_destroy_question: "Orri honek %{descendants} orri seme eta ondorengo ditu. Zer egin nahi duzu?" - text_wiki_page_nullify_children: "Orri semeak erro orri moduan mantendu" - text_wiki_page_destroy_children: "Orri semeak eta beraien ondorengo guztiak ezabatu" - text_wiki_page_reassign_children: "Orri semeak orri guraso honetara esleitu" - text_own_membership_delete_confirmation: "Zure baimen batzuk (edo guztiak) kentzera zoaz eta baliteke horren ondoren proiektu hau ezin editatzea.\n Ziur zaude jarraitu nahi duzula?" - - default_role_manager: Kudeatzailea - default_role_developer: Garatzailea - default_role_reporter: Berriemailea - default_tracker_bug: Errorea - default_tracker_feature: Eginbidea - default_tracker_support: Laguntza - default_issue_status_new: Berria - default_issue_status_in_progress: Lanean - default_issue_status_resolved: Ebatzita - default_issue_status_feedback: Berrelikadura - default_issue_status_closed: Itxita - default_issue_status_rejected: Baztertua - default_doc_category_user: Erabiltzaile dokumentazioa - default_doc_category_tech: Dokumentazio teknikoa - default_priority_low: Baxua - default_priority_normal: Normala - default_priority_high: Altua - default_priority_urgent: Larria - default_priority_immediate: Berehalakoa - default_activity_design: Diseinua - default_activity_development: Garapena - - enumeration_issue_priorities: Zeregin lehentasunak - enumeration_doc_categories: Dokumentu kategoriak - enumeration_activities: Jarduerak (denbora kontrola)) - enumeration_system_activity: Sistemako Jarduera - label_board_sticky: Itsaskorra - label_board_locked: Blokeatuta - permission_export_wiki_pages: Wiki orriak esportatu - setting_cache_formatted_text: Formatudun testua katxeatu - permission_manage_project_activities: Proiektuaren jarduerak kudeatu - error_unable_delete_issue_status: Ezine da zereginaren egoera ezabatu - label_profile: Profila - permission_manage_subtasks: Azpiatazak kudeatu - field_parent_issue: Zeregin gurasoa - label_subtask_plural: Azpiatazak - label_project_copy_notifications: Proiektua kopiatzen den bitartean eposta jakinarazpenak bidali - error_can_not_delete_custom_field: Ezin da eremu pertsonalizatua ezabatu - error_unable_to_connect: Ezin da konektatu (%{value}) - error_can_not_remove_role: Rol hau erabiltzen hari da eta ezin da ezabatu. - error_can_not_delete_tracker: Aztarnari honek zereginak ditu eta ezin da ezabatu. - field_principal: Ekintzaile - label_my_page_block: "Nire orriko blokea" - notice_failed_to_save_members: "Kidea(k) gordetzean errorea: %{errors}." - text_zoom_out: Zooma txikiagotu - text_zoom_in: Zooma handiagotu - notice_unable_delete_time_entry: "Ezin da hautatutako denbora erregistroa ezabatu." - label_overall_spent_time: Igarotako denbora guztira - field_time_entries: "Denbora erregistratu" - project_module_gantt: Gantt - project_module_calendar: Egutegia - button_edit_associated_wikipage: "Esleitutako wiki orria editatu: %{page_title}" - field_text: Testu eremua - label_user_mail_option_only_owner: "Jabea naizen gauzetarako barrarik" - setting_default_notification_option: "Lehenetsitako ohartarazpen aukera" - label_user_mail_option_only_my_events: "Behatzen ditudan edo partaide naizen gauzetarako bakarrik" - label_user_mail_option_only_assigned: "Niri esleitutako gauzentzat bakarrik" - label_user_mail_option_none: "Gertakaririk ez" - field_member_of_group: "Esleituta duenaren taldea" - field_assigned_to_role: "Esleituta duenaren rola" - notice_not_authorized_archived_project: "Atzitu nahi duzun proiektua artxibatua izan da." - label_principal_search: "Bilatu erabiltzaile edo taldea:" - label_user_search: "Erabiltzailea bilatu:" - field_visible: Ikusgai - setting_emails_header: "Eposten goiburua" - setting_commit_logtime_activity_id: "Erregistratutako denboraren jarduera" - text_time_logged_by_changeset: "%{value} aldaketan egindakoa." - setting_commit_logtime_enabled: "Erregistrutako denbora gaitu" - notice_gantt_chart_truncated: Grafikoa moztu da bistara daitekeen elementuen kopuru maximoa gainditu delako (%{max}) - setting_gantt_items_limit: "Gantt grafikoan bistara daitekeen elementu kopuru maximoa" - field_warn_on_leaving_unsaved: Gorde gabeko testua duen orri batetik ateratzen naizenean ohartarazi - text_warn_on_leaving_unsaved: Uneko orritik joaten bazara gorde gabeko testua galduko da. - label_my_queries: Nire galdera pertsonalizatuak - text_journal_changed_no_detail: "%{label} eguneratuta" - label_news_comment_added: Berri batera iruzkina gehituta - button_expand_all: Guztia zabaldu - button_collapse_all: Guztia tolestu - label_additional_workflow_transitions_for_assignee: Erabiltzaileak esleitua duenean baimendutako transtsizio gehigarriak - label_additional_workflow_transitions_for_author: Erabiltzailea egilea denean baimendutako transtsizio gehigarriak - label_bulk_edit_selected_time_entries: Hautatutako denbora egunkariak batera editatu - text_time_entries_destroy_confirmation: Ziur zaude hautatutako denbora egunkariak ezabatu nahi dituzula? - label_role_anonymous: Ezezaguna - label_role_non_member: Ez kidea - label_issue_note_added: Oharra gehituta - label_issue_status_updated: Egoera eguneratuta - label_issue_priority_updated: Lehentasuna eguneratuta - label_issues_visibility_own: Erabiltzaileak sortu edo esleituta dituen zereginak - field_issues_visibility: Zeregin ikusgarritasuna - label_issues_visibility_all: Zeregin guztiak - permission_set_own_issues_private: Nork bere zereginak publiko edo pribatu jarri - field_is_private: Pribatu - permission_set_issues_private: Zereginak publiko edo pribatu jarri - label_issues_visibility_public: Pribatu ez diren zeregin guztiak - text_issues_destroy_descendants_confirmation: Honek %{count} azpiataza ezabatuko ditu baita ere. - field_commit_logs_encoding: Commit-en egunkarien kodetzea - field_scm_path_encoding: Bidearen kodeketa - text_scm_path_encoding_note: "Lehentsita: UTF-8" - field_path_to_repository: Biltegirako bidea - field_root_directory: Erro direktorioa - field_cvs_module: Modulua - field_cvsroot: CVSROOT - text_mercurial_repository_note: Biltegi locala (adib. /hgrepo, c:\hgrepo) - text_scm_command: Komandoa - text_scm_command_version: Bertsioa - 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 zeregina - one: 1 zeregina - other: "%{count} zereginak" - 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: guztiak - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Azpiproiektuekin - label_cross_project_tree: Proiektu zuhaitzarekin - label_cross_project_hierarchy: Proiektu Hierarkiarekin - label_cross_project_system: Proiektu guztiekin - 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: 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/da/dafecb9ad68a1547dfb840791b3055c95d90810f.svn-base --- a/.svn/pristine/da/dafecb9ad68a1547dfb840791b3055c95d90810f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,749 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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::GanttHelperTest < ActionView::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :journals, :journal_details, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :versions, - :groups_users - - include ApplicationHelper - include ProjectsHelper - include IssuesHelper - include ERB::Util - include Rails.application.routes.url_helpers - - def setup - setup_with_controller - User.current = User.find(1) - end - - def today - @today ||= Date.today - 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 = 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)) - 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!(: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!(: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!(: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 => (today + 7), :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 => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - end - - context "project" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.project-name a", /#{@project.name}/ - end - - should "have an indent of 4" do - @output_buffer = @gantt.subjects - assert_select "div.project-name[style*=left:4px]" - end - end - - context "version" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.version-name a", /#{@version.name}/ - end - - should "be indented 24 (one level)" do - @output_buffer = @gantt.subjects - assert_select "div.version-name[style*=left:24px]" - end - - context "without assigned issues" do - setup do - @version = Version.generate!(:effective_date => (today + 14), - :sharing => 'none', - :name => 'empty_version') - @project.versions << @version - end - - should "not be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0 - end - end - end - - context "issue" do - should "be rendered" do - @output_buffer = @gantt.subjects - assert_select "div.issue-subject", /#{@issue.subject}/ - end - - should "be indented 44 (two levels)" do - @output_buffer = @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.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 => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - end - - should "be rendered" do - @output_buffer = @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 => (today - 1), - :due_date => (today + 2)) - ) - @child2 = Issue.generate!( - attrs.merge(:subject => 'child2', - :parent_issue_id => @issue.id, - :start_date => today, - :due_date => (today + 7)) - ) - @grandchild = Issue.generate!( - attrs.merge(:subject => 'grandchild', - :parent_issue_id => @child1.id, - :start_date => (today - 1), - :due_date => (today + 2)) - ) - end - - should "indent subtasks" do - @output_buffer = @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/, @output_buffer - 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 => (today + 7)) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 1), - :due_date => (today + 7)) - @project.issues << @issue - @output_buffer = @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 - @output_buffer = @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 - @output_buffer = @gantt.subject_for_project(@project, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the project name" do - @output_buffer = @gantt.subject_for_project(@project, {:format => :html}) - assert_select 'div', :text => /#{@project.name}/ - end - - should "include a link to the project" do - @output_buffer = @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 => (today - 1)) - assert @project.reload.overdue?, "Need an overdue project for this test" - @output_buffer = @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 => (today - 1)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should_eventually "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=left:28px]", true, @output_buffer - end - - should_eventually "be the total delayed width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should_eventually "start from the starting point on the left" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=left:28px]", true, @output_buffer - end - - should_eventually "Be the total done width of the project" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=width:18px]", true, @output_buffer - 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', today) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting", false, @output_buffer - end - - should "appear at the starting point" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting[style*=left:28px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending", false, @output_buffer - end - - should "appear at the end of the date range" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending[style*=left:88px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should "show the project name" do - @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should_eventually "show the percent complete" do - @output_buffer = @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 => (today - 1)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#subject_for_version", - :tracker => @tracker, - :project => @project, - :start_date => today) - - end - - context ":html format" do - should "add an absolute positioned div" do - @output_buffer = @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 - @output_buffer = @gantt.subject_for_version(@version, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the version name" do - @output_buffer = @gantt.subject_for_version(@version, {:format => :html}) - assert_select 'div', :text => /#{@version.name}/ - end - - should "include a link to the version" do - @output_buffer = @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" - @output_buffer = @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" - @output_buffer = @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 => (today + 7)) - @project.versions << @version - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=left:28px]", true, @output_buffer - end - - should "be the total delayed width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=left:28px]", true, @output_buffer - end - - should "be the total done width of the version" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=width:16px]", true, @output_buffer - 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', today) - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting", false - end - - should "appear at the starting point" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting[style*=left:28px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @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 - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.ending[style*=left:88px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the version name" do - @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the percent complete" do - @output_buffer = @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 => (today - 3), - :due_date => (today - 1)) - @project.issues << @issue - end - - context ":html format" do - should "add an absolute positioned div" do - @output_buffer = @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 - @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the issue subject" do - @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html}) - assert_select 'div', :text => /#{@issue.subject}/ - end - - should "include a link to the issue" do - @output_buffer = @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" - @output_buffer = @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 => (today + 7)) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => (today - 7), - :due_date => (today + 7)) - @project.issues << @issue - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=left:28px]", true, @output_buffer - end - - should "be the total width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=width:58px]", true, @output_buffer - end - end - - context "late line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=left:28px]", true, @output_buffer - end - - should "be the total delayed width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=width:30px]", true, @output_buffer - end - end - - context "done line" do - should "start from the starting point on the left" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:28px]", true, @output_buffer - end - - should "be the total done width of the issue" do - @output_buffer = @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, @output_buffer - end - - should "not be the total done width if the chart starts after issue start date" do - create_gantt(@project, :date_from => (today - 5)) - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:0px]", true, @output_buffer - assert_select "div.task_done[style*=width:8px]", true, @output_buffer - end - - context "for completed issue" do - setup do - @issue.done_ratio = 100 - end - - should "be the total width of the issue" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:58px]", true, @output_buffer - end - - should "be the total width of the issue with due_date=start_date" do - @issue.due_date = @issue.start_date - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:2px]", true, @output_buffer - 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', (today - 14)) - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", true, @output_buffer - end - - should "show the issue status" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /#{@issue.status.name}/ - end - - should "show the percent complete" do - @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /30%/ - end - end - end - - should "have an issue tooltip" do - @output_buffer = @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/db/db1913fc4efda154284af35ccff3e42e0708bad8.svn-base --- a/.svn/pristine/db/db1913fc4efda154284af35ccff3e42e0708bad8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class ActivitiesControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :groups_users, - :enabled_modules, - :workflows, - :journals, :journal_details - - - def test_project_index - get :index, :id => 1, :with_subprojects => 0 - assert_response :success - 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})/, - } - } - } - end - - def test_project_index_with_invalid_project_id_should_respond_404 - get :index, :id => 299 - assert_response 404 - end - - def test_previous_project_index - get :index, :id => 1, :from => 2.days.ago.to_date - assert_response :success - 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/, - } - } - } - end - - def test_global_index - @request.session[:user_id] = 1 - get :index - assert_response :success - assert_template 'index' - assert_not_nil assigns(:events_by_day) - - 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/, - } - } - } - end - - def test_user_index - @request.session[:user_id] = 1 - get :index, :user_id => 2 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:events_by_day) - - assert_select 'h2 a[href=/users/2]', :text => 'John Smith' - - 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/, - } - } - } - end - - def test_user_index_with_invalid_user_id_should_respond_404 - get :index, :user_id => 299 - assert_response 404 - end - - def test_index_atom_feed - get :index, :format => 'atom', :with_subprojects => 0 - 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'}} - end - - def test_index_atom_feed_with_explicit_selection - get :index, :format => 'atom', :with_subprojects => 0, - :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 - - 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'}} - 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/ - end - - def test_index_should_show_private_notes_with_permission_only - journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Private notes with searchkeyword', :private_notes => true) - @request.session[:user_id] = 2 - - get :index - assert_response :success - assert_include journal, assigns(:events_by_day).values.flatten - - Role.find(1).remove_permission! :view_private_notes - get :index - assert_response :success - assert_not_include journal, assigns(:events_by_day).values.flatten - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/db/db2e1fbb9075824d262474bf28faea86eb05a3b7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/db/db2e1fbb9075824d262474bf28faea86eb05a3b7.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/db/db6739fa1d3b26b6fdedf7673d7ba723fd42d485.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/db/db6739fa1d3b26b6fdedf7673d7ba723fd42d485.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/db/db8f4e77d30a8490da79511166f71ee7a646520b.svn-base --- a/.svn/pristine/db/db8f4e77d30a8490da79511166f71ee7a646520b.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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) - 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) - @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/db/dbd27b2d43c3f418a10ceae5e4a72e0cd0b0a99c.svn-base --- a/.svn/pristine/db/dbd27b2d43c3f418a10ceae5e4a72e0cd0b0a99c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc0b4be494ea301b961f3a7c8e25f53fd78f87cd.svn-base --- a/.svn/pristine/dc/dc0b4be494ea301b961f3a7c8e25f53fd78f87cd.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,200 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * This file is part of DotClear. - * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All - * rights reserved. - * - * DotClear is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * DotClear is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with DotClear; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * ***** END LICENSE BLOCK ***** -*/ - -/* Modified by JP LANG for textile formatting */ - -// strong -jsToolBar.prototype.elements.strong = { - type: 'button', - title: 'Strong', - fn: { - wiki: function() { this.singleTag('*') } - } -} - -// em -jsToolBar.prototype.elements.em = { - type: 'button', - title: 'Italic', - fn: { - wiki: function() { this.singleTag("_") } - } -} - -// ins -jsToolBar.prototype.elements.ins = { - type: 'button', - title: 'Underline', - fn: { - wiki: function() { this.singleTag('+') } - } -} - -// del -jsToolBar.prototype.elements.del = { - type: 'button', - title: 'Deleted', - fn: { - wiki: function() { this.singleTag('-') } - } -} - -// code -jsToolBar.prototype.elements.code = { - type: 'button', - title: 'Code', - fn: { - wiki: function() { this.singleTag('@') } - } -} - -// spacer -jsToolBar.prototype.elements.space1 = {type: 'space'} - -// headings -jsToolBar.prototype.elements.h1 = { - type: 'button', - title: 'Heading 1', - fn: { - wiki: function() { - this.encloseLineSelection('h1. ', '',function(str) { - str = str.replace(/^h\d+\.\s+/, '') - return str; - }); - } - } -} -jsToolBar.prototype.elements.h2 = { - type: 'button', - title: 'Heading 2', - fn: { - wiki: function() { - this.encloseLineSelection('h2. ', '',function(str) { - str = str.replace(/^h\d+\.\s+/, '') - return str; - }); - } - } -} -jsToolBar.prototype.elements.h3 = { - type: 'button', - title: 'Heading 3', - fn: { - wiki: function() { - this.encloseLineSelection('h3. ', '',function(str) { - str = str.replace(/^h\d+\.\s+/, '') - return str; - }); - } - } -} - -// spacer -jsToolBar.prototype.elements.space2 = {type: 'space'} - -// ul -jsToolBar.prototype.elements.ul = { - type: 'button', - title: 'Unordered list', - fn: { - wiki: function() { - this.encloseLineSelection('','',function(str) { - str = str.replace(/\r/g,''); - return str.replace(/(\n|^)[#-]?\s*/g,"$1* "); - }); - } - } -} - -// ol -jsToolBar.prototype.elements.ol = { - type: 'button', - title: 'Ordered list', - fn: { - wiki: function() { - this.encloseLineSelection('','',function(str) { - str = str.replace(/\r/g,''); - return str.replace(/(\n|^)[*-]?\s*/g,"$1# "); - }); - } - } -} - -// spacer -jsToolBar.prototype.elements.space3 = {type: 'space'} - -// bq -jsToolBar.prototype.elements.bq = { - type: 'button', - title: 'Quote', - fn: { - wiki: function() { - this.encloseLineSelection('','',function(str) { - str = str.replace(/\r/g,''); - return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2"); - }); - } - } -} - -// unbq -jsToolBar.prototype.elements.unbq = { - type: 'button', - title: 'Unquote', - fn: { - wiki: function() { - this.encloseLineSelection('','',function(str) { - str = str.replace(/\r/g,''); - return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2"); - }); - } - } -} - -// pre -jsToolBar.prototype.elements.pre = { - type: 'button', - title: 'Preformatted text', - fn: { - wiki: function() { this.encloseLineSelection('
    \n', '\n
    ') } - } -} - -// spacer -jsToolBar.prototype.elements.space4 = {type: 'space'} - -// wiki page -jsToolBar.prototype.elements.link = { - type: 'button', - title: 'Wiki link', - fn: { - wiki: function() { this.encloseSelection("[[", "]]") } - } -} -// image -jsToolBar.prototype.elements.img = { - type: 'button', - title: 'Image', - fn: { - wiki: function() { this.encloseSelection("!", "!") } - } -} diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc4543a1450d54a0a0b39eacccb59d550f2a12bf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/dc/dc4543a1450d54a0a0b39eacccb59d550f2a12bf.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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::AuthenticationTest < Redmine::ApiTest::Base + fixtures :users + + def setup + Setting.rest_api_enabled = '1' + end + + def teardown + Setting.rest_api_enabled = '0' + end + + def test_api_request_should_not_use_user_session + log_user('jsmith', 'jsmith') + + get '/users/current' + assert_response :success + + get '/users/current.json' + assert_response 401 + end + + def test_api_should_accept_switch_user_header_for_admin_user + user = User.find(1) + su = User.find(4) + + get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login} + assert_response :success + assert_equal su, assigns(:user) + assert_equal su, User.current + end + + def test_api_should_respond_with_412_when_trying_to_switch_to_a_invalid_user + get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => 'foobar'} + assert_response 412 + end + + def test_api_should_respond_with_412_when_trying_to_switch_to_a_locked_user + user = User.find(5) + assert user.locked? + + get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => user.login} + assert_response 412 + end + + def test_api_should_not_accept_switch_user_header_for_non_admin_user + user = User.find(2) + su = User.find(4) + + get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login} + assert_response :success + assert_equal user, assigns(:user) + assert_equal user, User.current + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc488722255cd0f93614e33a6e9db17ff574d2f6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/dc/dc488722255cd0f93614e33a6e9db17ff574d2f6.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,132 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc54398fd56016bd2c6a2c422b0739908a6738fe.svn-base --- a/.svn/pristine/dc/dc54398fd56016bd2c6a2c422b0739908a6738fe.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,369 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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_equal [], 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_equal [], 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_done_ratio_with_child_estimate_to_0_should_reach_100 - parent = Issue.generate! - issue1 = Issue.generate!(:parent_issue_id => parent.id) - issue2 = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 0) - assert_equal 0, parent.reload.done_ratio - issue1.reload.update_attribute :status_id, 5 - assert_equal 50, parent.reload.done_ratio - issue2.reload.update_attribute :status_id, 5 - assert_equal 100, 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc67b5d95ecc47650b97552f261cf812282703c9.svn-base --- a/.svn/pristine/dc/dc67b5d95ecc47650b97552f261cf812282703c9.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1070 +0,0 @@ -nl: - 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: "%d %B, %Y" - - day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag] - abbr_day_names: [zo, ma, di, wo, do, vr, za] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, mei, jun, jul, aug, sep, okt, nov, dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%e %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "halve minuut" - less_than_x_seconds: - one: "minder dan een seconde" - other: "minder dan %{count} seconden" - x_seconds: - one: "1 seconde" - other: "%{count} seconden" - less_than_x_minutes: - one: "minder dan een minuut" - other: "minder dan %{count} minuten" - x_minutes: - one: "1 minuut" - other: "%{count} minuten" - about_x_hours: - one: "ongeveer 1 uur" - other: "ongeveer %{count} uren" - x_hours: - one: "1 uur" - other: "%{count} uren" - x_days: - one: "1 dag" - other: "%{count} dagen" - about_x_months: - one: "ongeveer 1 maand" - other: "ongeveer %{count} maanden" - x_months: - one: "1 maand" - other: "%{count} maanden" - about_x_years: - one: "ongeveer 1 jaar" - other: "ongeveer %{count} jaar" - over_x_years: - one: "meer dan 1 jaar" - other: "meer dan %{count} jaar" - almost_x_years: - one: "bijna 1 jaar" - other: "bijna %{count} jaar" - - 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: "en" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "Door een fout kon dit %{model} niet worden opgeslagen" - other: "Door %{count} fouten kon dit %{model} niet worden opgeslagen" - messages: - inclusion: "staat niet in de lijst" - exclusion: "is gereserveerd" - invalid: "is ongeldig" - confirmation: "komt niet overeen met bevestiging" - accepted: "moet geaccepteerd worden" - empty: "mag niet leeg zijn" - blank: "mag niet blanco zijn" - too_long: "is te lang" - too_short: "is te kort" - wrong_length: "heeft een onjuiste lengte" - taken: "is al in gebruik" - not_a_number: "is geen getal" - not_a_date: "is geen valide datum" - greater_than: "moet groter zijn dan %{count}" - greater_than_or_equal_to: "moet groter zijn of gelijk zijn aan %{count}" - equal_to: "moet gelijk zijn aan %{count}" - less_than: "moet minder zijn dan %{count}" - less_than_or_equal_to: "moet minder dan of gelijk zijn aan %{count}" - odd: "moet oneven zijn" - even: "moet even zijn" - greater_than_start_date: "moet na de startdatum liggen" - not_same_project: "hoort niet bij hetzelfde project" - circular_dependency: "Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben" - cant_link_an_issue_with_a_descendant: "Een issue kan niet gelinked worden met een subtask" - - actionview_instancetag_blank_option: Selecteer - - button_activate: Activeer - button_add: Voeg toe - button_annotate: Annoteer - button_apply: Pas toe - button_archive: Archiveer - button_back: Terug - button_cancel: Annuleer - button_change: Wijzig - button_change_password: Wijzig wachtwoord - button_check_all: Selecteer alle - button_clear: Leeg maken - button_configure: Configureer - button_copy: Kopiëer - button_create: Maak - button_delete: Verwijder - button_download: Download - button_edit: Bewerk - button_list: Lijst - button_lock: Sluit - button_log_time: Registreer tijd - button_login: Inloggen - button_move: Verplaatsen - button_quote: Citaat - button_rename: Hernoemen - button_reply: Antwoord - button_reset: Reset - button_rollback: Rollback naar deze versie - button_save: Bewaren - button_sort: Sorteer - button_submit: Toevoegen - button_test: Test - button_unarchive: Dearchiveer - button_uncheck_all: Deselecteer alle - button_unlock: Open - button_unwatch: Niet meer monitoren - button_update: Update - button_view: Bekijken - button_watch: Monitor - default_activity_design: Ontwerp - default_activity_development: Ontwikkeling - default_doc_category_tech: Technische documentatie - default_doc_category_user: Gebruikersdocumentatie - default_issue_status_in_progress: In Progress - default_issue_status_closed: Gesloten - default_issue_status_feedback: Terugkoppeling - default_issue_status_new: Nieuw - default_issue_status_rejected: Afgewezen - default_issue_status_resolved: Opgelost - default_priority_high: Hoog - default_priority_immediate: Onmiddellijk - default_priority_low: Laag - default_priority_normal: Normaal - default_priority_urgent: Spoed - default_role_developer: Ontwikkelaar - default_role_manager: Manager - default_role_reporter: Rapporteur - default_tracker_bug: Bug - default_tracker_feature: Feature - default_tracker_support: Support - enumeration_activities: Activiteiten (tijdtracking) - enumeration_doc_categories: Documentcategorieën - enumeration_issue_priorities: Issueprioriteiten - error_can_t_load_default_data: "De standaard configuratie kon niet worden geladen: %{value}" - error_issue_not_found_in_project: 'Deze issue is niet gevonden of behoort niet toe tot dit project.' - error_scm_annotate: "Er kan geen commentaar toegevoegd worden." - error_scm_command_failed: "Er trad een fout op tijdens de poging om verbinding te maken met de repository: %{value}" - error_scm_not_found: "Deze ingang of revisie bestaat niet in de repository." - field_account: Account - field_activity: Activiteit - field_admin: Beheerder - field_assignable: Issues kunnen toegewezen worden aan deze rol - field_assigned_to: Toegewezen aan - field_attr_firstname: Voornaam attribuut - field_attr_lastname: Achternaam attribuut - field_attr_login: Login attribuut - field_attr_mail: E-mail attribuut - field_auth_source: Authenticatiemethode - field_author: Auteur - field_base_dn: Base DN - field_category: Categorie - field_column_names: Kolommen - field_comments: Commentaar - field_comments_sorting: Commentaar weergeven - field_created_on: Aangemaakt - field_default_value: Standaardwaarde - field_delay: Vertraging - field_description: Beschrijving - field_done_ratio: "% Gereed" - field_downloads: Downloads - field_due_date: Verwachte datum gereed - field_effective_date: Datum - field_estimated_hours: Geschatte tijd - field_field_format: Formaat - field_filename: Bestand - field_filesize: Grootte - field_firstname: Voornaam - field_fixed_version: Versie - field_hide_mail: Verberg mijn e-mailadres - field_homepage: Homepage - field_host: Host - field_hours: Uren - field_identifier: Identificatiecode - field_is_closed: Issue gesloten - field_is_default: Standaard - field_is_filter: Gebruikt als een filter - field_is_for_all: Voor alle projecten - field_is_in_roadmap: Issues weergegeven in roadmap - field_is_public: Publiek - field_is_required: Verplicht - field_issue: Issue - field_issue_to: Gerelateerd issue - field_language: Taal - field_last_login_on: Laatste bezoek - field_lastname: Achternaam - field_login: Gebruikersnaam - field_mail: E-mail - field_mail_notification: Mail notificaties - field_max_length: Maximale lengte - field_min_length: Minimale lengte - field_name: Naam - field_new_password: Nieuw wachtwoord - field_notes: Notities - field_onthefly: On-the-fly aanmaken van een gebruiker - field_parent: Subproject van - field_parent_title: Bovenliggende pagina - field_password: Wachtwoord - field_password_confirmation: Bevestig wachtwoord - field_port: Port - field_possible_values: Mogelijke waarden - field_priority: Prioriteit - field_project: Project - field_redirect_existing_links: Verwijs bestaande links door - field_regexp: Reguliere expressie - field_role: Rol - field_searchable: Doorzoekbaar - field_spent_on: Datum - field_start_date: Startdatum - field_start_page: Startpagina - field_status: Status - field_subject: Onderwerp - field_subproject: Subproject - field_summary: Samenvatting - field_time_zone: Tijdzone - field_title: Titel - field_tracker: Tracker - field_type: Type - field_updated_on: Gewijzigd - field_url: URL - field_user: Gebruiker - field_value: Waarde - field_version: Versie - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-1 - general_csv_separator: ';' - general_first_day_of_week: '7' - general_lang_name: 'Nederlands' - general_pdf_encoding: UTF-8 - general_text_No: 'Nee' - general_text_Yes: 'Ja' - general_text_no: 'nee' - general_text_yes: 'ja' - label_activity: Activiteit - label_add_another_file: Ander bestand toevoegen - label_add_note: Voeg een notitie toe - label_added: toegevoegd - label_added_time_by: "Toegevoegd door %{author} %{age} geleden" - label_administration: Administratie - label_age: Leeftijd - label_ago: dagen geleden - label_all: alle - label_all_time: alles - label_all_words: Alle woorden - label_and_its_subprojects: "%{value} en zijn subprojecten." - label_applied_status: Toegekende status - label_assigned_to_me_issues: Aan mij toegewezen issues - label_associated_revisions: Geassociëerde revisies - label_attachment: Bestand - label_attachment_delete: Verwijder bestand - label_attachment_new: Nieuw bestand - label_attachment_plural: Bestanden - label_attribute: Attribuut - label_attribute_plural: Attributen - label_auth_source: Authenticatiemodus - label_auth_source_new: Nieuwe authenticatiemodus - label_auth_source_plural: Authenticatiemodi - label_authentication: Authenticatie - label_blocked_by: geblokkeerd door - label_blocks: blokkeert - label_board: Forum - label_board_new: Nieuw forum - label_board_plural: Forums - label_boolean: Boolean - label_browse: Blader - label_bulk_edit_selected_issues: Bewerk geselecteerde issues in bulk - label_calendar: Kalender - label_change_plural: Wijzigingen - label_change_properties: Eigenschappen wijzigen - label_change_status: Wijzig status - label_change_view_all: Bekijk alle wijzigingen - label_changes_details: Details van alle wijzigingen - label_changeset_plural: Changesets - label_chronological_order: In chronologische volgorde - label_closed_issues: gesloten - label_closed_issues_plural: gesloten - label_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 gesloten - one: 1 gesloten - other: "%{count} gesloten" - label_comment: Commentaar - label_comment_add: Voeg commentaar toe - label_comment_added: Commentaar toegevoegd - label_comment_delete: Verwijder commentaar - label_comment_plural: Commentaar - label_x_comments: - zero: geen commentaar - one: 1x commentaar - other: "%{count}x commentaar" - label_commits_per_author: Commits per auteur - label_commits_per_month: Commits per maand - label_confirmation: Bevestiging - label_contains: bevat - label_copied: gekopieerd - label_copy_workflow_from: Kopieer workflow van - label_current_status: Huidige status - label_current_version: Huidige versie - label_custom_field: Specifiek veld - label_custom_field_new: Nieuw specifiek veld - label_custom_field_plural: Specifieke velden - label_date: Datum - label_date_from: Van - label_date_range: Datumbereik - label_date_to: Tot - label_day_plural: dagen - label_default: Standaard - label_default_columns: Standaard kolommen. - label_deleted: verwijderd - label_details: Details - label_diff_inline: inline - label_diff_side_by_side: naast elkaar - label_disabled: uitgeschakeld - label_display_per_page: "Per pagina: %{value}" - label_document: Document - label_document_added: Document toegevoegd - label_document_new: Nieuw document - label_document_plural: Documenten - label_downloads_abbr: D/L - label_duplicated_by: gedupliceerd door - label_duplicates: dupliceert - label_end_to_end: eind tot eind - label_end_to_start: eind tot start - label_enumeration_new: Nieuwe waarde - label_enumerations: Enumeraties - label_environment: Omgeving - label_equals: is gelijk - label_example: Voorbeeld - label_export_to: Exporteer naar - label_f_hour: "%{value} uur" - label_f_hour_plural: "%{value} uren" - label_feed_plural: Feeds - label_feeds_access_key_created_on: "RSS toegangssleutel %{value} geleden gemaakt." - label_file_added: Bestand toegevoegd - label_file_plural: Bestanden - label_filter_add: Voeg filter toe - label_filter_plural: Filters - label_float: Float - label_follows: volgt op - label_gantt: Gantt - label_general: Algemeen - label_generate_key: Genereer een sleutel - label_help: Help - label_history: Geschiedenis - label_home: Home - label_in: in - label_in_less_than: in minder dan - label_in_more_than: in meer dan - label_incoming_emails: Inkomende e-mail - label_index_by_date: Indexeer op datum - label_index_by_title: Indexeer op titel - label_information: Informatie - label_information_plural: Informatie - label_integer: Integer - label_internal: Intern - label_issue: Incident - label_issue_added: Incident toegevoegd - label_issue_category: Incident categorie - label_issue_category_new: Nieuwe categorie - label_issue_category_plural: Issuecategorieën - label_issue_new: Nieuw incident - label_issue_plural: Incidenten - label_issue_status: Incident status - label_issue_status_new: Nieuwe status - label_issue_status_plural: Incident statussen - label_issue_tracking: Incident-tracking - label_issue_updated: Incident bijgewerkt - label_issue_view_all: Bekijk alle incidenten - label_issue_watchers: Monitoren - label_issues_by: "Issues door %{value}" - label_jump_to_a_project: Ga naar een project... - label_language_based: Taal gebaseerd - label_last_changes: "laatste %{count} wijzigingen" - label_last_login: Laatste bezoek - label_last_month: laatste maand - label_last_n_days: "%{count} dagen geleden" - label_last_week: vorige week - label_latest_revision: Meest recente revisie - label_latest_revision_plural: Meest recente revisies - label_ldap_authentication: LDAP authenticatie - label_less_than_ago: minder dan x dagen geleden - label_list: Lijst - label_loading: Laden... - label_logged_as: Ingelogd als - label_login: Inloggen - label_logout: Uitloggen - label_max_size: Maximumgrootte - label_me: mij - label_member: Lid - label_member_new: Nieuw lid - label_member_plural: Leden - label_message_last: Laatste bericht - label_message_new: Nieuw bericht - label_message_plural: Berichten - label_message_posted: Bericht toegevoegd - label_min_max_length: Min-max lengte - label_modified: gewijzigd - label_module_plural: Modules - label_month: Maand - label_months_from: maanden vanaf - label_more: Meer - label_more_than_ago: meer dan x dagen geleden - label_my_account: Mijn account - label_my_page: Mijn pagina - label_my_projects: Mijn projecten - label_new: Nieuw - label_new_statuses_allowed: Nieuwe toegestane statussen - label_news: Nieuws - label_news_added: Nieuws toegevoegd - label_news_latest: Laatste nieuws - label_news_new: Voeg nieuws toe - label_news_plural: Nieuws - label_news_view_all: Bekijk al het nieuws - label_next: Volgende - label_no_change_option: (Geen wijziging) - label_no_data: Geen gegevens om te tonen - label_nobody: niemand - label_none: geen - label_not_contains: bevat niet - label_not_equals: is niet gelijk - label_open_issues: open - label_open_issues_plural: open - label_optional_description: Optionele beschrijving - label_options: Opties - label_overall_activity: Activiteit - label_overview: Overzicht - label_password_lost: Wachtwoord verloren - label_per_page: Per pagina - label_permissions: Permissies - label_permissions_report: Permissierapport - label_personalize_page: Personaliseer deze pagina - label_planning: Planning - label_please_login: Log a.u.b. in - label_plugins: Plugins - label_precedes: gaat vooraf aan - label_preferences: Voorkeuren - label_preview: Voorbeeldweergave - label_previous: Vorige - label_project: Project - label_project_all: Alle projecten - label_project_latest: Nieuwste projecten - label_project_new: Nieuw project - label_project_plural: Projecten - label_x_projects: - zero: geen projecten - one: 1 project - other: "%{count} projecten" - label_public_projects: Publieke projecten - label_query: Eigen zoekopdracht - label_query_new: Nieuwe zoekopdracht - label_query_plural: Eigen zoekopdrachten - label_read: Lees... - label_register: Registreer - label_registered_on: Geregistreerd op - label_registration_activation_by_email: accountactivatie per e-mail - label_registration_automatic_activation: automatische accountactivatie - label_registration_manual_activation: handmatige accountactivatie - label_related_issues: Gerelateerde issues - label_relates_to: gerelateerd aan - label_relation_delete: Verwijder relatie - label_relation_new: Nieuwe relatie - label_renamed: hernoemd - label_reply_plural: Antwoorden - label_report: Rapport - label_report_plural: Rapporten - label_reported_issues: Gemelde issues - label_repository: Repository - label_repository_plural: Repositories - label_result_plural: Resultaten - label_reverse_chronological_order: In omgekeerde chronologische volgorde - label_revision: Revisie - label_revision_plural: Revisies - label_roadmap: Roadmap - label_roadmap_due_in: "Voldaan in %{value}" - label_roadmap_no_issues: Geen issues voor deze versie - label_roadmap_overdue: "%{value} over tijd" - label_role: Rol - label_role_and_permissions: Rollen en permissies - label_role_new: Nieuwe rol - label_role_plural: Rollen - label_scm: SCM - label_search: Zoeken - label_search_titles_only: Enkel titels doorzoeken - label_send_information: Stuur accountinformatie naar de gebruiker - label_send_test_email: Stuur een test e-mail - label_settings: Instellingen - label_show_completed_versions: Toon afgeronde versies - label_sort_by: "Sorteer op %{value}" - label_sort_higher: Verplaats naar boven - label_sort_highest: Verplaats naar begin - label_sort_lower: Verplaats naar beneden - label_sort_lowest: Verplaats naar eind - label_spent_time: Gespendeerde tijd - label_start_to_end: start tot eind - label_start_to_start: start tot start - label_statistics: Statistieken - label_stay_logged_in: Blijf ingelogd - label_string: Tekst - label_subproject_plural: Subprojecten - label_text: Lange tekst - label_theme: Thema - label_this_month: deze maand - label_this_week: deze week - label_this_year: dit jaar - label_time_tracking: Tijdregistratie bijhouden - label_today: vandaag - label_topic_plural: Onderwerpen - label_total: Totaal - label_tracker: Tracker - label_tracker_new: Nieuwe tracker - label_tracker_plural: Trackers - label_updated_time: "%{value} geleden bijgewerkt" - label_updated_time_by: "%{age} geleden bijgewerkt door %{author}" - label_used_by: Gebruikt door - label_user: Gebruiker - label_user_activity: "%{value}'s activiteit" - label_user_mail_no_self_notified: Ik wil niet op de hoogte gehouden worden van mijn eigen wijzigingen - label_user_mail_option_all: "Bij elk gebeurtenis in al mijn projecten..." - label_user_mail_option_selected: "Enkel bij elke gebeurtenis op het geselecteerde project..." - label_user_new: Nieuwe gebruiker - label_user_plural: Gebruikers - label_version: Versie - label_version_new: Nieuwe versie - label_version_plural: Versies - label_view_diff: Bekijk verschillen - label_view_revisions: Bekijk revisies - label_watched_issues: Gemonitorde issues - label_week: Week - label_wiki: Wiki - label_wiki_edit: Wiki edit - label_wiki_edit_plural: Wiki edits - label_wiki_page: Wikipagina - label_wiki_page_plural: Wikipagina's - label_workflow: Workflow - label_year: Jaar - label_yesterday: gisteren - mail_body_account_activation_request: "Een nieuwe gebruiker (%{value}) is geregistreerd. Zijn account wacht op uw akkoord:" - mail_body_account_information: Uw account gegevens - mail_body_account_information_external: "U kunt uw account (%{value}) gebruiken om in te loggen." - mail_body_lost_password: 'Gebruik de volgende link om uw wachtwoord te wijzigen:' - mail_body_register: 'Gebruik de volgende link om uw account te activeren:' - mail_body_reminder: "%{count} issue(s) die aan u toegewezen zijn en voldaan moeten zijn in de komende %{days} dagen:" - mail_subject_account_activation_request: "%{value} accountactivatieverzoek" - mail_subject_lost_password: "uw %{value} wachtwoord" - mail_subject_register: "uw %{value} accountactivatie" - mail_subject_reminder: "%{count} issue(s) die voldaan moeten zijn in de komende %{days} dagen." - notice_account_activated: uw account is geactiveerd. u kunt nu inloggen. - notice_account_invalid_creditentials: Incorrecte gebruikersnaam of wachtwoord - notice_account_lost_email_sent: Er is een e-mail naar u verstuurd met instructies over het kiezen van een nieuw wachtwoord. - notice_account_password_updated: Wachtwoord is met succes gewijzigd - notice_account_pending: "Uw account is aangemaakt, maar wacht nog op goedkeuring van de beheerder." - notice_account_register_done: Account is met succes aangemaakt. - notice_account_unknown_email: Onbekende gebruiker. - notice_account_updated: Account is met succes gewijzigd - notice_account_wrong_password: Incorrect wachtwoord - notice_can_t_change_password: Dit account gebruikt een externe bron voor authenticatie. Het is niet mogelijk om het wachtwoord te veranderen. - notice_default_data_loaded: Standaard configuratie succesvol geladen. - notice_email_error: "Er is een fout opgetreden tijdens het versturen van (%{value})" - notice_email_sent: "Een e-mail werd verstuurd naar %{value}" - notice_failed_to_save_issues: "Fout bij bewaren van %{count} issue(s) (%{total} geselecteerd): %{ids}." - notice_feeds_access_key_reseted: Je RSS toegangssleutel werd gereset. - notice_file_not_found: De pagina die u probeerde te benaderen bestaat niet of is verwijderd. - notice_locking_conflict: De gegevens zijn gewijzigd door een andere gebruiker. - notice_no_issue_selected: "Er is geen issue geselecteerd. Selecteer de issue die u wilt bewerken." - notice_not_authorized: Het is u niet toegestaan deze pagina te raadplegen. - notice_successful_connection: Verbinding succesvol. - notice_successful_create: Succesvol aangemaakt. - notice_successful_delete: Succesvol verwijderd. - notice_successful_update: Wijzigen succesvol. - notice_unable_delete_version: Niet mogelijk om deze versie te verwijderen. - permission_add_issue_notes: Voeg notities toe - permission_add_issue_watchers: Voeg monitors toe - permission_add_issues: Voeg issues toe - permission_add_messages: Voeg berichten toe - permission_browse_repository: Repository doorbladeren - permission_comment_news: Nieuws commentaar geven - permission_commit_access: Commit toegang - permission_delete_issues: Issues verwijderen - permission_delete_messages: Berichten verwijderen - permission_delete_own_messages: Eigen berichten verwijderen - permission_delete_wiki_pages: Wiki pagina's verwijderen - permission_delete_wiki_pages_attachments: Bijlagen verwijderen - permission_edit_issue_notes: Notities bewerken - permission_edit_issues: Issues bewerken - permission_edit_messages: Berichten bewerken - permission_edit_own_issue_notes: Eigen notities bewerken - permission_edit_own_messages: Eigen berichten bewerken - permission_edit_own_time_entries: Eigen tijdlogboek bewerken - permission_edit_project: Project bewerken - permission_edit_time_entries: Tijdlogboek bewerken - permission_edit_wiki_pages: Wiki pagina's bewerken - permission_log_time: Gespendeerde tijd loggen - permission_manage_boards: Forums beheren - permission_manage_categories: Issue-categorieën beheren - permission_manage_files: Bestanden beheren - permission_manage_issue_relations: Issuerelaties beheren - permission_manage_members: Leden beheren - permission_manage_news: Nieuws beheren - permission_manage_public_queries: Publieke queries beheren - permission_manage_repository: Repository beheren - permission_manage_versions: Versiebeheer - permission_manage_wiki: Wikibeheer - permission_move_issues: Issues verplaatsen - permission_protect_wiki_pages: Wikipagina's beschermen - permission_rename_wiki_pages: Wikipagina's hernoemen - permission_save_queries: Queries opslaan - permission_select_project_modules: Project modules selecteren - permission_view_calendar: Kalender bekijken - permission_view_changesets: Changesets bekijken - permission_view_documents: Documenten bekijken - permission_view_files: Bestanden bekijken - permission_view_gantt: Gantt grafiek bekijken - permission_view_issue_watchers: Monitorlijst bekijken - permission_view_messages: Berichten bekijken - permission_view_time_entries: Gespendeerde tijd bekijken - permission_view_wiki_edits: Wikihistorie bekijken - permission_view_wiki_pages: Wikipagina's bekijken - project_module_boards: Forums - project_module_documents: Documenten - project_module_files: Bestanden - project_module_issue_tracking: Issue tracking - project_module_news: Nieuws - project_module_repository: Repository - project_module_time_tracking: Tijd tracking - project_module_wiki: Wiki - setting_activity_days_default: Aantal dagen getoond bij het tabblad "Activiteit" - setting_app_subtitle: Applicatieondertitel - setting_app_title: Applicatietitel - setting_attachment_max_size: Attachment max. grootte - setting_autofetch_changesets: Haal commits automatisch op - setting_autologin: Automatisch inloggen - setting_bcc_recipients: Blind carbon copy ontvangers (bcc) - setting_commit_fix_keywords: Gefixeerde trefwoorden - setting_commit_ref_keywords: Refererende trefwoorden - setting_cross_project_issue_relations: Sta cross-project issuerelaties toe - setting_date_format: Datumformaat - setting_default_language: Standaard taal - setting_default_projects_public: Nieuwe projecten zijn standaard publiek - setting_diff_max_lines_displayed: Max aantal diff regels weer te geven - setting_display_subprojects_issues: Standaard issues van subproject tonen - setting_emails_footer: E-mails voettekst - setting_enabled_scm: SCM ingeschakeld - setting_feeds_limit: Feedinhoudlimiet - setting_gravatar_enabled: Gebruik Gravatar gebruikersiconen - setting_host_name: Hostnaam - setting_issue_list_default_columns: Standaardkolommen getoond op de lijst met issues - setting_issues_export_limit: Max aantal te exporteren issues - setting_login_required: Authenticatie vereist - setting_mail_from: Afzender e-mail adres - setting_mail_handler_api_enabled: Schakel WS in voor inkomende mail. - setting_mail_handler_api_key: API sleutel - setting_per_page_options: Aantal objecten per pagina (opties) - setting_plain_text_mail: platte tekst (geen HTML) - setting_protocol: Protocol - setting_self_registration: Zelfregistratie toegestaan - setting_sequential_project_identifiers: Genereer sequentiële projectidentiteiten - setting_sys_api_enabled: Gebruik WS voor repository beheer - setting_text_formatting: Tekstformaat - setting_time_format: Tijd formaat - setting_user_format: Gebruikers weergaveformaat - setting_welcome_text: Welkomsttekst - setting_wiki_compression: Wikigeschiedenis comprimeren - status_active: actief - status_locked: vergrendeld - status_registered: geregistreerd - text_are_you_sure: Weet u het zeker? - text_assign_time_entries_to_project: Gerapporteerde uren toevoegen aan dit project - text_caracters_maximum: "%{count} van maximum aantal tekens." - text_caracters_minimum: "Moet minstens %{count} karakters lang zijn." - text_comma_separated: Meerdere waarden toegestaan (kommagescheiden). - text_default_administrator_account_changed: Standaard beheerderaccount gewijzigd - text_destroy_time_entries: Verwijder gerapporteerde uren - text_destroy_time_entries_question: "%{hours} uren werden gerapporteerd op de issue(s) die u wilde verwijderen. Wat wil u doen?" - text_diff_truncated: '... Deze diff werd afgekort omdat het de maximale weer te geven karakters overschreed.' - text_email_delivery_not_configured: "E-mailbezorging is niet geconfigureerd. Mededelingen zijn uitgeschakeld.\nConfigureer uw SMTP server in config/configuration.yml en herstart de applicatie om dit te activeren." - text_enumeration_category_reassign_to: 'Wijs de volgende waarde toe:' - text_enumeration_destroy_question: "%{count} objecten zijn toegewezen aan deze waarde." - text_file_repository_writable: Bestandsrepository beschrijfbaar - text_issue_added: "Issue %{id} is gerapporteerd (door %{author})." - text_issue_category_destroy_assignments: Verwijder toewijzingen aan deze categorie - text_issue_category_destroy_question: "Er zijn issues (%{count}) aan deze categorie toegewezen. Wat wilt u hiermee doen ?" - text_issue_category_reassign_to: Issues opnieuw toewijzen aan deze categorie - text_issue_updated: "Issue %{id} is gewijzigd (door %{author})." - text_issues_destroy_confirmation: 'Weet u zeker dat u deze issue(s) wil verwijderen?' - text_issues_ref_in_commit_messages: Opzoeken en aanpassen van issues in commitberichten - text_length_between: "Lengte tussen %{min} en %{max} tekens." - text_load_default_configuration: Laad de standaardconfiguratie - text_min_max_length_info: 0 betekent geen restrictie - text_no_configuration_data: "Rollen, trackers, issue statussen en workflows zijn nog niet geconfigureerd.\nHet is ten zeerste aangeraden om de standaard configuratie in te laden. U kunt deze aanpassen nadat deze is ingeladen." - text_plugin_assets_writable: Plugin assets directory beschrijfbaar - text_project_destroy_confirmation: Weet u zeker dat u dit project en alle gerelateerde gegevens wilt verwijderen? - text_project_identifier_info: 'Alleen kleine letter (a-z), cijfers, streepjes en liggende streepjes zijn toegestaan.
    Eenmaal opgeslagen kan de identifier niet worden gewijzigd.' - text_reassign_time_entries: 'Gerapporteerde uren opnieuw toewijzen:' - text_regexp_info: bv. ^[A-Z0-9]+$ - text_repository_usernames_mapping: "Koppel de Redminegebruikers aan gebruikers in de repository log.\nGebruikers met dezelfde Redmine en repository gebruikersnaam of email worden automatisch gekoppeld." - text_rmagick_available: RMagick beschikbaar (optioneel) - text_select_mail_notifications: Selecteer acties waarvoor mededelingen via mail moeten worden verstuurd. - text_select_project_modules: 'Selecteer de modules die u wilt gebruiken voor dit project:' - text_status_changed_by_changeset: "Toegepast in changeset %{value}." - text_subprojects_destroy_warning: "De subprojecten: %{value} zullen ook verwijderd worden." - text_tip_issue_begin_day: issue die op deze dag begint - text_tip_issue_begin_end_day: issue die op deze dag begint en eindigt - text_tip_issue_end_day: issue die op deze dag eindigt - text_tracker_no_workflow: Geen workflow gedefinieerd voor deze tracker - text_unallowed_characters: Niet toegestane tekens - text_user_mail_option: "Bij niet-geselecteerde projecten zult u enkel mededelingen ontvangen voor issues die u monitort of waar u bij betrokken bent (als auteur of toegewezen persoon)." - text_user_wrote: "%{value} schreef:" - text_wiki_destroy_confirmation: Weet u zeker dat u deze wiki en zijn inhoud wenst te verwijderen? - text_workflow_edit: Selecteer een rol en een tracker om de workflow te wijzigen - warning_attachments_not_saved: "%{count} bestand(en) konden niet opgeslagen worden." - button_create_and_continue: Maak en ga verder - text_custom_field_possible_values_info: 'Per lijn een waarde' - label_display: Toon - field_editable: Bewerkbaar - setting_repository_log_display_limit: Max aantal revisies zichbaar - setting_file_max_size_displayed: Max grootte van tekst bestanden inline zichtbaar - field_watcher: Watcher - setting_openid: Sta OpenID login en registratie toe - field_identity_url: OpenID URL - label_login_with_open_id_option: of login met je OpenID - field_content: Content - label_descending: Aflopend - label_sort: Sorteer - label_ascending: Oplopend - label_date_from_to: Van %{start} tot %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Deze pagina heeft %{descendants} subpagina's en onderliggende pagina's?. Wat wilt u hiermee doen? - text_wiki_page_reassign_children: Alle subpagina's toewijzen aan deze hoofdpagina - text_wiki_page_nullify_children: Behoud subpagina's als hoofdpagina's - text_wiki_page_destroy_children: Verwijder alle subpagina's en onderliggende pagina's - setting_password_min_length: Minimum wachtwoord lengte - field_group_by: Groepeer resultaten per - mail_subject_wiki_content_updated: "'%{id}' wiki pagina is bijgewerkt" - label_wiki_content_added: Wiki pagina toegevoegd - mail_subject_wiki_content_added: "'%{id}' wiki pagina is toegevoegd" - mail_body_wiki_content_added: De '%{id}' wiki pagina is toegevoegd door %{author}. - label_wiki_content_updated: Wiki pagina bijgewerkt - mail_body_wiki_content_updated: De '%{id}' wiki pagina is bijgewerkt door %{author}. - permission_add_project: Maak project - setting_new_project_user_role_id: Rol van gebruiker die een project maakt - label_view_all_revisions: Bekijk alle revisies - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Geen tracker is geassocieerd met dit project. Check de project instellingen. - error_no_default_issue_status: Geen standaard issue status ingesteld. Check de configuratie (Ga naar "Administratie -> Issue statussen"). - text_journal_changed: "%{label} gewijzigd van %{old} naar %{new}" - text_journal_set_to: "%{label} gewijzigd naar %{value}" - text_journal_deleted: "%{label} verwijderd (%{old})" - label_group_plural: Groepen - label_group: Groep - label_group_new: Nieuwe groep - label_time_entry_plural: Bestede tijd - text_journal_added: "%{label} %{value} toegevoegd" - field_active: Actief - enumeration_system_activity: Systeem Activiteit - permission_delete_issue_watchers: Verwijder volgers - version_status_closed: gesloten - version_status_locked: vergrendeld - version_status_open: open - error_can_not_reopen_issue_on_closed_version: Een issue toegewezen aan een gesloten versie kan niet heropend worden - label_user_anonymous: Anoniem - button_move_and_follow: Verplaats en volg - setting_default_projects_modules: Standaard geactiveerde modules voor nieuwe projecten - setting_gravatar_default: Standaard Gravatar plaatje - field_sharing: Delen - label_version_sharing_hierarchy: Met project hiërarchie - label_version_sharing_system: Met alle projecten - label_version_sharing_descendants: Met subprojecten - label_version_sharing_tree: Met project boom - label_version_sharing_none: Niet gedeeld - error_can_not_archive_project: Dit project kan niet worden gearchiveerd - button_duplicate: Dupliceer - button_copy_and_follow: Kopiëer en volg - label_copy_source: Bron - setting_issue_done_ratio: Bereken issue percentage voldaan met - setting_issue_done_ratio_issue_status: Gebruik de issue status - error_issue_done_ratios_not_updated: Issue percentage voldaan niet geupdate. - error_workflow_copy_target: Selecteer tracker(s) en rol(len) - setting_issue_done_ratio_issue_field: Gebruik het issue veld - label_copy_same_as_target: Zelfde als doel - label_copy_target: Doel - notice_issue_done_ratios_updated: Issue percentage voldaan geupdate. - error_workflow_copy_source: Selecteer een bron tracker of rol - label_update_issue_done_ratios: Update issue percentage voldaan - setting_start_of_week: Week begint op - permission_view_issues: Bekijk Issues - label_display_used_statuses_only: Laat alleen statussen zien die gebruikt worden door deze tracker - label_revision_id: Revisie %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key gemaakt %{value} geleden - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Uw API access key was gereset. - setting_rest_api_enabled: Activeer REST web service - label_missing_api_access_key: Geen API access key - label_missing_feeds_access_key: Geen RSS access key - button_show: Laat zien - text_line_separated: Meerdere waarden toegestaan (elke regel is een waarde). - setting_mail_handler_body_delimiters: Breek email verwerking af na een van deze regels - permission_add_subprojects: Maak subprojecten - label_subproject_new: Nieuw subproject - text_own_membership_delete_confirmation: |- - U staat op punt om sommige of alle van uw permissies te verwijderen en bent mogelijk niet meer toegestaan om dit project hierna te wijzigen. - Wilt u doorgaan? - label_close_versions: Sluit complete versies - label_board_sticky: Sticky - label_board_locked: Vergrendeld - permission_export_wiki_pages: Exporteer wiki pagina's - setting_cache_formatted_text: Cache opgemaakte tekst - permission_manage_project_activities: Beheer project activiteiten - error_unable_delete_issue_status: Verwijderen van issue status niet gelukt - label_profile: Profiel - permission_manage_subtasks: Beheer subtaken - field_parent_issue: Hoofdtaak - label_subtask_plural: Subtaken - label_project_copy_notifications: Stuur email notificaties voor de project kopie - error_can_not_delete_custom_field: Verwijderen niet mogelijk van custom field - error_unable_to_connect: Geen connectie (%{value}) - error_can_not_remove_role: Deze rol is in gebruik en kan niet worden verwijderd. - error_can_not_delete_tracker: Deze tracker bevat nog issues en kan niet worden verwijderd. - field_principal: Hoofd - label_my_page_block: Mijn pagina block - notice_failed_to_save_members: "Niet gelukt om lid/leden op te slaan: %{errors}." - text_zoom_out: Zoom uit - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Verwijderen niet mogelijk van tijd log invoer. - label_overall_spent_time: Totaal bestede tijd - field_time_entries: Registreer tijd - project_module_gantt: Gantt - project_module_calendar: Kalender - button_edit_associated_wikipage: "Bewerk bijbehorende wiki pagina: %{page_title}" - field_text: Tekst veld - label_user_mail_option_only_owner: Alleen voor dingen waarvan ik de auteur ben - setting_default_notification_option: Standaard instelling voor mededelingen - label_user_mail_option_only_my_events: Alleen voor dingen die ik volg of bij betrokken ben - label_user_mail_option_only_assigned: Alleen voor dingen die aan mij zijn toegewezen - label_user_mail_option_none: Bij geen enkele gebeurtenis - field_member_of_group: Groep van toegewezene - field_assigned_to_role: Rol van toegewezene - notice_not_authorized_archived_project: Het project dat u wilt bezoeken is gearchiveerd. - label_principal_search: "Zoek naar gebruiker of groep:" - label_user_search: "Zoek naar gebruiker:" - field_visible: Zichtbaar - setting_commit_logtime_activity_id: Standaard activiteit voor tijdregistratie - text_time_logged_by_changeset: Toegepast in changeset %{value}. - setting_commit_logtime_enabled: Activeer tijdregistratie - notice_gantt_chart_truncated: De gantt chart is ingekort omdat het meer objecten bevat dan kan worden weergegeven, (%{max}) - setting_gantt_items_limit: Max. aantal objecten op gantt chart - field_warn_on_leaving_unsaved: Waarschuw me wanneer ik een pagina verlaat waarvan de tekst niet opgeslagen is - text_warn_on_leaving_unsaved: De huidige pagina bevat tekst die niet is opgeslagen en dit zal verloren gaan als u deze pagina nu verlaat. - label_my_queries: Mijn aangepaste zoekopdrachten - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Commentaar toegevoegd aan een nieuwsitem - button_expand_all: Klap uit - button_collapse_all: Klap in - label_additional_workflow_transitions_for_assignee: Aanvullende veranderingen toegestaan wanneer de gebruiker de toegewezene is - label_additional_workflow_transitions_for_author: Aanvullende veranderingen toegestaan wanneer de gebruiker de auteur is - label_bulk_edit_selected_time_entries: Massa wijziging geselecteerd tijd-registraties? - text_time_entries_destroy_confirmation: Weet u zeker dat u de geselecteerde item(s) wilt verwijderen ? - label_role_anonymous: Anoniem - label_role_non_member: Geen lid - label_issue_note_added: Notitie toegevoegd - label_issue_status_updated: Status gewijzigd - label_issue_priority_updated: Prioriteit geupdate - label_issues_visibility_own: Probleem aangemaakt door of toegewezen aan - field_issues_visibility: Incidenten weergave - label_issues_visibility_all: Alle incidenten - permission_set_own_issues_private: Zet eigen incidenten publiekelijk of privé - field_is_private: Privé - permission_set_issues_private: Zet incidenten publiekelijk of privé - label_issues_visibility_public: Alle niet privé-incidenten - text_issues_destroy_descendants_confirmation: Dit zal ook de %{count} subtaken verwijderen. - field_commit_logs_encoding: Encodering van commit berichten - field_scm_path_encoding: Pad encodering - text_scm_path_encoding_note: "Standaard: UTF-8" - field_path_to_repository: Pad naar versie overzicht - field_root_directory: Root directorie - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: "Lokale versie overzicht (Voorbeeld: /hgrepo, c:\\hgrepo)" - text_scm_command: Commando - text_scm_command_version: Versie - label_git_report_last_commit: Rapporteer laatste toevoegen voor bestanden en directories - text_scm_config: U kan de scm commando's configureren in config/configuration.yml. Herstart de applicatie na het wijzigen ervan. - text_scm_command_not_available: Scm commando is niet beschikbaar. Controleer de instellingen in het administratiepaneel. - notice_issue_successful_create: Probleem %{id} aangemaakt. - label_between: tussen - setting_issue_group_assignment: Sta groepstoewijzingen toe - label_diff: Verschil - text_git_repository_note: "Versie overzicht lokaal is leeg (Voorbeeld: /gitrepo, c:\\gitrepo)" - description_query_sort_criteria_direction: Sortering - description_project_scope: Zoek bereik - description_filter: Filter - description_user_mail_notification: Mail notificatie instellingen - description_date_from: Vul start datum in - description_message_content: Inhoud bericht - description_available_columns: Beschikbare kolommen - description_date_range_interval: Kies een bereik bij het selecteren van een start en eind datum - description_issue_category_reassign: Kies probleem categorie - description_search: Zoekveld - description_notes: Notities - description_date_range_list: Kies bereik vanuit de lijst - description_choose_project: Projecten - description_date_to: Vul eind datum in - description_query_sort_criteria_attribute: Sorteer attribuut - description_wiki_subpages_reassign: Kies nieuwe hoofdpagina - description_selected_columns: Geselecteerde kolommen - label_parent_revision: Hoofd - label_child_revision: Sub - error_scm_annotate_big_text_file: De vermelding kan niet worden geannoteerd, omdat het groter is dan de maximale toegewezen grootte. - setting_default_issue_start_date_to_creation_date: Gebruik huidige datum als start datum voor nieuwe incidenten. - button_edit_section: Wijzig deze sectie - setting_repositories_encodings: Bijlage en opgeslagen bestanden coderingen - description_all_columns: Alle kolommen - button_export: Exporteren - label_export_options: "%{export_format} export opties" - error_attachment_too_big: Dit bestand kan niet worden geupload omdat het de maximaal toegestane grootte overschrijd (%{max_size}) - notice_failed_to_save_time_entries: "Opslaan gefaald voor %{count} tijdsnotatie(s) van %{total} geselecteerde: %{ids}." - label_x_issues: - zero: 0 incidenten - one: 1 incidenten - other: "%{count} incidenten" - label_repository_new: Nieuw repository - field_repository_is_default: Hoofd repository - label_copy_attachments: Copieer bijlage(n) - label_item_position: "%{position}/%{count}" - label_completed_versions: Versies compleet - field_multiple: Meerdere waardes - setting_commit_cross_project_ref: Sta toe om incidenten van alle projecten te refereren en oplossen - text_issue_conflict_resolution_add_notes: Voeg mijn notities toe en annuleer andere wijzigingen - text_issue_conflict_resolution_overwrite: Voeg mijn wijzigingen alsnog toe (voorgaande notities worden bewaard, maar sommige kunnen overschreden worden) - notice_issue_update_conflict: Dit incident is reeds geupdate door een andere gebruiker terwijl jij bezig was - text_issue_conflict_resolution_cancel: Annuleer mijn wijzigingen en geef pagina opnieuw weer %{link} - permission_manage_related_issues: Beheer gerelateerde incidenten - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Zoek om monitoorders toe te voegen - notice_account_deleted: Uw account is permanent verwijderd - setting_unsubscribe: Sta gebruikers toe hun eigen account te verwijderen - button_delete_my_account: Verwijder mijn account - text_account_destroy_confirmation: |- - Weet u zeker dat u door wilt gaan? - Uw account wordt permanent verwijderd zonder mogelijkheid deze te heractiveren. - error_session_expired: Uw sessie is verlopen. U dient opnieuw in te loggen. - text_session_expiration_settings: "Waarschuwing: door deze instelling te wijzigen kan sessies laten verlopen inclusief de uwe" - setting_session_lifetime: Maximale sessieduur - setting_session_timeout: Sessie inactiviteit timeout - label_session_expiration: Sessie verlopen - permission_close_project: Sluit / heropen project - label_show_closed_projects: Gesloten projecten weergeven - button_close: Sluiten - button_reopen: Heropen - project_status_active: actief - project_status_closed: gesloten - project_status_archived: gearchiveerd - 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 seconden) - setting_thumbnails_enabled: Geef bijlage miniaturen weer - setting_thumbnails_size: Grootte miniaturen (in pixels) - label_status_transitions: Status transitie - label_fields_permissions: Permissie velden - label_readonly: Alleen-lezen - label_required: Verplicht - text_repository_identifier_info: 'Alleen kleine letter (a-z), cijfers, streepjes en liggende streepjes zijn toegestaan.
    Eenmaal opgeslagen kan de identifier niet worden gewijzigd.' - field_board_parent: Hoofd forum - label_attribute_of_project: Project %{name} - label_attribute_of_author: Auteur(s) %{name} - label_attribute_of_assigned_to: Toegewezen %{name} - label_attribute_of_fixed_version: Target versions %{name} - label_copy_subtasks: Kopieer subtaken - 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: 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: 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: 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc6b5b76f3744d49697cec4f0525f325d4065393.svn-base --- a/.svn/pristine/dc/dc6b5b76f3744d49697cec4f0525f325d4065393.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc9139c99c509d9f76df794ebe1b204339e929fd.svn-base --- a/.svn/pristine/dc/dc9139c99c509d9f76df794ebe1b204339e929fd.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -
    -<%= link_to l(:label_role_new), new_role_path, :class => 'icon icon-add' %> -<%= link_to l(:label_permissions_report), permissions_roles_path, :class => 'icon icon-summary' %> -
    - -

    <%=l(:label_role_plural)%>

    - - - - - - - - -<% for role in @roles %> - "> - - - - -<% end %> - -
    <%=l(:label_role)%><%=l(:button_sort)%>
    <%= content_tag(role.builtin? ? 'em' : 'span', link_to(h(role.name), edit_role_path(role))) %> - <% unless role.builtin? %> - <%= reorder_links('role', {:action => 'update', :id => role}, :put) %> - <% end %> - - <%= link_to l(:button_copy), new_role_path(:copy => role), :class => 'icon icon-copy' %> - <%= delete_link role_path(role) unless role.builtin? %> -
    - -

    <%= pagination_links_full @role_pages %>

    - -<% html_title(l(:label_role_plural)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc922e7ccd955065ada49a909cc92f6e9ee69e68.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/dc/dc922e7ccd955065ada49a909cc92f6e9ee69e68.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc9491f2af49f910aa61b48511dec77f59aca42c.svn-base --- a/.svn/pristine/dc/dc9491f2af49f910aa61b48511dec77f59aca42c.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 teardown - Setting.clear_cache - end - - 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 - - def test_per_page_options_array_should_be_an_empty_array_when_setting_is_blank - with_settings :per_page_options => nil do - assert_equal [], Setting.per_page_options_array - end - - with_settings :per_page_options => '' do - assert_equal [], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_an_array_of_integers - with_settings :per_page_options => '10, 25, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_omit_non_numerial_values - with_settings :per_page_options => 'a, 25, 50' do - assert_equal [25, 50], Setting.per_page_options_array - end - end - - def test_per_page_options_array_should_be_sorted - with_settings :per_page_options => '25, 10, 50' do - assert_equal [10, 25, 50], Setting.per_page_options_array - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/dc/dc9b1cbad00145256a0e92e2088ee0a8b20dc51e.svn-base --- a/.svn/pristine/dc/dc9b1cbad00145256a0e92e2088ee0a8b20dc51e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1181 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 ERB::Util - - fixtures :projects, :roles, :enabled_modules, :users, - :repositories, :changesets, - :trackers, :issue_statuses, :issues, :versions, :documents, - :wikis, :wiki_pages, :wiki_contents, - :boards, :messages, :news, - :attachments, :enumerations - - def setup - super - set_tmp_attachments_directory - end - - context "#link_to_if_authorized" do - context "authorized user" do - should "be tested" - end - - context "unauthorized user" do - should "be tested" - end - - should "allow using the :controller and :action for the target link" do - User.current = User.find_by_login('admin') - - @project = Issue.first.project # Used by helper - response = link_to_if_authorized("By controller/action", - {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) - assert_match /href/, response - end - - end - - def test_auto_links - to_test = { - 'http://foo.bar' => 'http://foo.bar', - 'http://foo.bar/~user' => 'http://foo.bar/~user', - 'http://foo.bar.' => 'http://foo.bar.', - 'https://foo.bar.' => 'https://foo.bar.', - 'This is a link: http://foo.bar.' => 'This is a link: http://foo.bar.', - 'A link (eg. http://foo.bar).' => 'A link (eg. http://foo.bar).', - 'http://foo.bar/foo.bar#foo.bar.' => 'http://foo.bar/foo.bar#foo.bar.', - 'http://www.foo.bar/Test_(foobar)' => 'http://www.foo.bar/Test_(foobar)', - '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : http://www.foo.bar/Test_(foobar))', - '(see inline link : http://www.foo.bar/Test)' => '(see inline link : http://www.foo.bar/Test)', - '(see inline link : http://www.foo.bar/Test).' => '(see inline link : http://www.foo.bar/Test).', - '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see inline link)', - '(see "inline link":http://www.foo.bar/Test)' => '(see inline link)', - '(see "inline link":http://www.foo.bar/Test).' => '(see inline link).', - 'www.foo.bar' => 'www.foo.bar', - 'http://foo.bar/page?p=1&t=z&s=' => 'http://foo.bar/page?p=1&t=z&s=', - 'http://foo.bar/page#125' => 'http://foo.bar/page#125', - 'http://foo@www.bar.com' => 'http://foo@www.bar.com', - 'http://foo:bar@www.bar.com' => 'http://foo:bar@www.bar.com', - 'ftp://foo.bar' => 'ftp://foo.bar', - 'ftps://foo.bar' => 'ftps://foo.bar', - 'sftp://foo.bar' => 'sftp://foo.bar', - # two exclamation marks - 'http://example.net/path!602815048C7B5C20!302.html' => 'http://example.net/path!602815048C7B5C20!302.html', - # escaping - 'http://foo"bar' => 'http://foo"bar', - # wrap in angle brackets - '' => '<http://foo.bar>' - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - if 'ruby'.respond_to?(:encoding) - def test_auto_links_with_non_ascii_characters - to_test = { - 'http://foo.bar/тест' => 'http://foo.bar/тест' - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - else - puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version' - end - - def test_auto_mailto - assert_equal '

    ', - textilizable('test@foo.bar') - end - - def test_inline_images - to_test = { - '!http://foo.bar/image.jpg!' => '', - 'floating !>http://foo.bar/image.jpg!' => 'floating
    ', - 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class ', - 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style ', - 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title This is a title', - 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title This is a double-quoted "title"', - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - def test_inline_images_inside_tags - raw = <<-RAW -h1. !foo.png! Heading - -Centered image: - -p=. !bar.gif! -RAW - - assert textilizable(raw).include?('') - assert textilizable(raw).include?('') - end - - 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', - 'No match: !ogo.gif!' => 'No match: ', - 'No match: !ogo.GIF!' => 'No match: ', - # link image - '!logo.gif!:http://foo.bar/' => 'This is a logo', - } - attachments = Attachment.find(:all) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text, :attachments => attachments) } - end - - def test_attached_images_filename_extension - set_tmp_attachments_directory - a1 = Attachment.new( - :container => Issue.find(1), - :file => mock_file_with_options({:original_filename => "testtest.JPG"}), - :author => User.find(1)) - assert a1.save - assert_equal "testtest.JPG", a1.filename - assert_equal "image/jpeg", a1.content_type - assert a1.image? - - a2 = Attachment.new( - :container => Issue.find(1), - :file => mock_file_with_options({:original_filename => "testtest.jpeg"}), - :author => User.find(1)) - assert a2.save - assert_equal "testtest.jpeg", a2.filename - assert_equal "image/jpeg", a2.content_type - assert a2.image? - - a3 = Attachment.new( - :container => Issue.find(1), - :file => mock_file_with_options({:original_filename => "testtest.JPE"}), - :author => User.find(1)) - assert a3.save - assert_equal "testtest.JPE", a3.filename - assert_equal "image/jpeg", a3.content_type - assert a3.image? - - a4 = Attachment.new( - :container => Issue.find(1), - :file => mock_file_with_options({:original_filename => "Testtest.BMP"}), - :author => User.find(1)) - assert a4.save - assert_equal "Testtest.BMP", a4.filename - assert_equal "image/x-ms-bmp", a4.content_type - assert a4.image? - - to_test = { - 'Inline image: !testtest.jpg!' => - 'Inline image: ', - 'Inline image: !testtest.jpeg!' => - 'Inline image: ', - 'Inline image: !testtest.jpe!' => - 'Inline image: ', - 'Inline image: !testtest.bmp!' => - 'Inline image: ', - } - - attachments = [a1, a2, a3, a4] - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text, :attachments => attachments) } - end - - def test_attached_images_should_read_later - set_fixtures_attachments_directory - 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 - - to_test = { - 'Inline image: !testfile.png!' => - 'Inline image: ', - 'Inline image: !Testfile.PNG!' => - 'Inline image: ', - } - attachments = [a1, a2] - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text, :attachments => attachments) } - set_tmp_attachments_directory - end - - def test_textile_external_links - to_test = { - 'This is a "link":http://foo.bar' => 'This is a link', - 'This is an intern "link":/foo/bar' => 'This is an intern link', - '"link (Link title)":http://foo.bar' => 'link', - '"link (Link title with "double-quotes")":http://foo.bar' => 'link', - "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":

    \n\n\n\t

    Another paragraph", - # no multiline link text - "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line
    and another on a second line\":test", - # mailto link - "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "system administrator", - # two exclamation marks - '"a link":http://example.net/path!602815048C7B5C20!302.html' => 'a link', - # escaping - '"test":http://foo"bar' => 'test', - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - if 'ruby'.respond_to?(:encoding) - def test_textile_external_links_with_non_ascii_characters - to_test = { - 'This is a "link":http://foo.bar/тест' => 'This is a link' - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - else - puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version' - end - - 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)') - 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)') - - 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}, - :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') - - document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1}, - :class => 'document') - - version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2}, - :class => 'version') - - board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'} - - message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4} - - news_url = {:controller => 'news', :action => 'show', :id => 1} - - project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'} - - source_url = '/projects/ecookbook/repository/entry/some/file' - 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' - - 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' - - to_test = { - # tickets - '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.", - # ticket notes - '#3-14' => note_link, - '#3#note-14' => note_link, - # 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}", - # documents - 'document#1' => document_link, - 'document:"Test document"' => document_link, - # versions - 'version#2' => version_link, - 'version:1.0' => version_link, - 'version:"1.0"' => version_link, - # source - 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'), - 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'), - 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".", - '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.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.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'), - 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'), - # export - 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'), - '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'), - # forum - 'forum#2' => link_to('Discussion', board_url, :class => 'board'), - 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'), - # message - 'message#4' => link_to('Post 2', message_url, :class => 'message'), - 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'), - # news - 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'), - 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'), - # project - 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), - 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), - 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), - # not found - '#0123456789' => '#0123456789', - # invalid expressions - 'source:' => 'source:', - # url hash - "http://foo.bar/FAQ#3" => 'http://foo.bar/FAQ#3', - } - @project = Project.find(1) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text), "#{text} failed" } - end - - def test_redmine_links_with_a_different_project_before_current_project - vp1 = Version.generate!(:project_id => 1, :name => '1.4.4') - vp3 = Version.generate!(:project_id => 3, :name => '1.4.4') - - @project = Project.find(3) - assert_equal %(

    1.4.4 1.4.4

    ), - textilizable("ecookbook:version:1.4.4 version:1.4.4") - end - - def test_escaped_redmine_links_should_not_be_parsed - to_test = [ - '#3.', - '#3-14.', - '#3#-note14.', - 'r1', - 'document#1', - 'document:"Test document"', - 'version#2', - 'version:1.0', - 'version:"1.0"', - 'source:/some/file' - ] - @project = Project.find(1) - to_test.each { |text| assert_equal "

    #{text}

    ", textilizable("!" + text), "#{text} failed" } - end - - def test_cross_project_redmine_links - source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, - :class => 'source') - - changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, - :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') - - to_test = { - # documents - 'document:"Test document"' => 'document:"Test document"', - 'ecookbook:document:"Test document"' => 'Test document', - 'invalid:document:"Test document"' => 'invalid:document:"Test document"', - # versions - 'version:"1.0"' => 'version:"1.0"', - 'ecookbook:version:"1.0"' => '1.0', - 'invalid:version:"1.0"' => 'invalid:version:"1.0"', - # changeset - 'r2' => 'r2', - 'ecookbook:r2' => changeset_link, - 'invalid:r2' => 'invalid:r2', - # source - 'source:/some/file' => 'source:/some/file', - 'ecookbook:source:/some/file' => source_link, - 'invalid:source:/some/file' => 'invalid:source:/some/file', - } - @project = Project.find(3) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text), "#{text} failed" } - end - - def test_multiple_repositories_redmine_links - svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg') - Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123') - hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg') - Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd') - - changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, - :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') - svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123}, - :class => 'changeset', :title => '') - hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'}, - :class => 'changeset', :title => '') - - source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source') - hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source') - - to_test = { - 'r2' => changeset_link, - 'svn_repo-1|r123' => svn_changeset_link, - 'invalid|r123' => 'invalid|r123', - 'commit:hg1|abcd' => hg_changeset_link, - 'commit:invalid|abcd' => 'commit:invalid|abcd', - # source - 'source:some/file' => source_link, - 'source:hg1|some/file' => hg_source_link, - 'source:invalid|some/file' => 'source:invalid|some/file', - } - - @project = Project.find(1) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text), "#{text} failed" } - end - - def test_cross_project_multiple_repositories_redmine_links - svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg') - Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123') - hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg') - Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd') - - changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, - :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') - svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123}, - :class => 'changeset', :title => '') - hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'}, - :class => 'changeset', :title => '') - - source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source') - hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source') - - to_test = { - 'ecookbook:r2' => changeset_link, - 'ecookbook:svn1|r123' => svn_changeset_link, - 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123', - 'ecookbook:commit:hg1|abcd' => hg_changeset_link, - 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd', - 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd', - # source - 'ecookbook:source:some/file' => source_link, - 'ecookbook:source:hg1|some/file' => hg_source_link, - 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file', - 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file', - } - - @project = Project.find(3) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text), "#{text} failed" } - end - - def test_redmine_links_git_commit - changeset_link = link_to('abcd', - { - :controller => 'repositories', - :action => 'revision', - :id => 'subproject1', - :rev => 'abcd', - }, - :class => 'changeset', :title => 'test commit') - to_test = { - 'commit:abcd' => changeset_link, - } - @project = Project.find(3) - r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git') - assert r - c = Changeset.new(:repository => r, - :committed_on => Time.now, - :revision => 'abcd', - :scmid => 'abcd', - :comments => 'test commit') - assert( c.save ) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'. - def test_redmine_links_darcs_commit - changeset_link = link_to('20080308225258-98289-abcd456efg.gz', - { - :controller => 'repositories', - :action => 'revision', - :id => 'subproject1', - :rev => '123', - }, - :class => 'changeset', :title => 'test commit') - to_test = { - 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link, - } - @project = Project.find(3) - r = Repository::Darcs.create!( - :project => @project, :url => '/tmp/test/darcs', - :log_encoding => 'UTF-8') - assert r - c = Changeset.new(:repository => r, - :committed_on => Time.now, - :revision => '123', - :scmid => '20080308225258-98289-abcd456efg.gz', - :comments => 'test commit') - assert( c.save ) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - def test_redmine_links_mercurial_commit - changeset_link_rev = link_to('r123', - { - :controller => 'repositories', - :action => 'revision', - :id => 'subproject1', - :rev => '123' , - }, - :class => 'changeset', :title => 'test commit') - changeset_link_commit = link_to('abcd', - { - :controller => 'repositories', - :action => 'revision', - :id => 'subproject1', - :rev => 'abcd' , - }, - :class => 'changeset', :title => 'test commit') - to_test = { - 'r123' => changeset_link_rev, - 'commit:abcd' => changeset_link_commit, - } - @project = Project.find(3) - r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test') - assert r - c = Changeset.new(:repository => r, - :committed_on => Time.now, - :revision => '123', - :scmid => 'abcd', - :comments => 'test commit') - assert( c.save ) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - 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 - } - to_test.each { |text, result| assert_equal "

    #{result}

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

    test.txt

    ), - textilizable('attachment:test.txt', :attachments => [a1, a2]) - end - - def test_wiki_links - to_test = { - '[[CookBook documentation]]' => 'CookBook documentation', - '[[Another page|Page]]' => 'Page', - # title content should be formatted - '[[Another page|With _styled_ *title*]]' => 'With styled title', - '[[Another page|With title containing HTML entities & markups]]' => 'With title containing <strong>HTML entities & markups</strong>', - # link with anchor - '[[CookBook documentation#One-section]]' => 'CookBook documentation', - '[[Another page#anchor|Page]]' => 'Page', - # UTF8 anchor - '[[Another_page#Тест|Тест]]' => %|Тест|, - # page that doesn't exist - '[[Unknown page]]' => 'Unknown page', - '[[Unknown page|404]]' => '404', - # link to another project wiki - '[[onlinestore:]]' => 'onlinestore', - '[[onlinestore:|Wiki]]' => 'Wiki', - '[[onlinestore:Start page]]' => 'Start page', - '[[onlinestore:Start page|Text]]' => 'Text', - '[[onlinestore:Unknown page]]' => 'Unknown page', - # striked through link - '-[[Another page|Page]]-' => 'Page', - '-[[Another page|Page]] link-' => 'Page link', - # escaping - '![[Another page|Page]]' => '[[Another page|Page]]', - # project does not exist - '[[unknowproject:Start]]' => '[[unknowproject:Start]]', - '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]', - } - - @project = Project.find(1) - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - def test_wiki_links_within_local_file_generation_context - - to_test = { - # link to a page - '[[CookBook documentation]]' => 'CookBook documentation', - '[[CookBook documentation|documentation]]' => 'documentation', - '[[CookBook documentation#One-section]]' => 'CookBook documentation', - '[[CookBook documentation#One-section|documentation]]' => 'documentation', - # page that doesn't exist - '[[Unknown page]]' => 'Unknown page', - '[[Unknown page|404]]' => '404', - '[[Unknown page#anchor]]' => 'Unknown page', - '[[Unknown page#anchor|404]]' => '404', - } - - @project = Project.find(1) - - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text, :wiki_links => :local) } - end - - def test_wiki_links_within_wiki_page_context - - page = WikiPage.find_by_title('Another_page' ) - - to_test = { - # link to another page - '[[CookBook documentation]]' => 'CookBook documentation', - '[[CookBook documentation|documentation]]' => 'documentation', - '[[CookBook documentation#One-section]]' => 'CookBook documentation', - '[[CookBook documentation#One-section|documentation]]' => 'documentation', - # link to the current page - '[[Another page]]' => 'Another page', - '[[Another page|Page]]' => 'Page', - '[[Another page#anchor]]' => 'Another page', - '[[Another page#anchor|Page]]' => 'Page', - # page that doesn't exist - '[[Unknown page]]' => 'Unknown page', - '[[Unknown page|404]]' => '404', - '[[Unknown page#anchor]]' => 'Unknown page', - '[[Unknown page#anchor|404]]' => '404', - } - - @project = Project.find(1) - - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(WikiContent.new( :text => text, :page => page ), :text) } - end - - def test_wiki_links_anchor_option_should_prepend_page_title_to_href - - to_test = { - # link to a page - '[[CookBook documentation]]' => 'CookBook documentation', - '[[CookBook documentation|documentation]]' => 'documentation', - '[[CookBook documentation#One-section]]' => 'CookBook documentation', - '[[CookBook documentation#One-section|documentation]]' => 'documentation', - # page that doesn't exist - '[[Unknown page]]' => 'Unknown page', - '[[Unknown page|404]]' => '404', - '[[Unknown page#anchor]]' => 'Unknown page', - '[[Unknown page#anchor|404]]' => '404', - } - - @project = Project.find(1) - - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text, :wiki_links => :anchor) } - end - - def test_html_tags - to_test = { - "
    content
    " => "

    <div>content</div>

    ", - "
    content
    " => "

    <div class=\"bold\">content</div>

    ", - "" => "

    <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 -->

    ", - "" => "

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

    ", - " Status masalah").' - error_can_not_reopen_issue_on_closed_version: 'Masalah yang ditujukan pada versi tertutup tidak bisa dibuka kembali' - error_can_not_archive_project: Proyek ini tidak bisa diarsipkan - - warning_attachments_not_saved: "%{count} berkas tidak bisa disimpan." - - mail_subject_lost_password: "Kata sandi %{value} anda" - mail_body_lost_password: 'Untuk mengubah kata sandi anda, klik tautan berikut::' - mail_subject_register: "Aktivasi akun %{value} anda" - mail_body_register: 'Untuk mengaktifkan akun anda, klik tautan berikut:' - mail_body_account_information_external: "Anda dapat menggunakan akun %{value} anda untuk login." - mail_body_account_information: Informasi akun anda - mail_subject_account_activation_request: "Permintaan aktivasi akun %{value} " - mail_body_account_activation_request: "Pengguna baru (%{value}) sudan didaftarkan. Akun tersebut menunggu persetujuan anda:" - mail_subject_reminder: "%{count} masalah harus selesai pada hari berikutnya (%{days})" - mail_body_reminder: "%{count} masalah yang ditugaskan pada anda harus selesai dalam %{days} hari kedepan:" - mail_subject_wiki_content_added: "'%{id}' halaman wiki sudah ditambahkan" - mail_body_wiki_content_added: "The '%{id}' halaman wiki sudah ditambahkan oleh %{author}." - 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 - field_summary: Ringkasan - field_is_required: Dibutuhkan - field_firstname: Nama depan - field_lastname: Nama belakang - field_mail: Email - field_filename: Berkas - field_filesize: Ukuran - field_downloads: Unduhan - field_author: Pengarang - field_created_on: Dibuat - field_updated_on: Diperbarui - field_field_format: Format - field_is_for_all: Untuk semua proyek - field_possible_values: Nilai yang mungkin - field_regexp: Regular expression - field_min_length: Panjang minimum - field_max_length: Panjang maksimum - field_value: Nilai - field_category: Kategori - field_title: Judul - field_project: Proyek - field_issue: Masalah - field_status: Status - field_notes: Catatan - field_is_closed: Masalah ditutup - field_is_default: Nilai default - field_tracker: Pelacak - field_subject: Perihal - field_due_date: Harus selesai - field_assigned_to: Ditugaskan ke - field_priority: Prioritas - field_fixed_version: Versi target - field_user: Pengguna - field_role: Peran - field_homepage: Halaman web - field_is_public: Publik - field_parent: Subproyek dari - field_is_in_roadmap: Masalah ditampilkan di rencana kerja - field_login: Login - field_mail_notification: Notifikasi email - field_admin: Administrator - field_last_login_on: Terakhir login - field_language: Bahasa - field_effective_date: Tanggal - field_password: Kata sandi - field_new_password: Kata sandi baru - field_password_confirmation: Konfirmasi - field_version: Versi - field_type: Tipe - field_host: Host - field_port: Port - field_account: Akun - field_base_dn: Base DN - field_attr_login: Atribut login - field_attr_firstname: Atribut nama depan - field_attr_lastname: Atribut nama belakang - field_attr_mail: Atribut email - field_onthefly: Pembuatan pengguna seketika - field_start_date: Mulai - field_done_ratio: "% Selesai" - field_auth_source: Mode otentikasi - field_hide_mail: Sembunyikan email saya - field_comments: Komentar - field_url: URL - field_start_page: Halaman awal - field_subproject: Subproyek - field_hours: Jam - field_activity: Kegiatan - field_spent_on: Tanggal - field_identifier: Pengenal - field_is_filter: Digunakan sebagai penyaring - field_issue_to: Masalah terkait - field_delay: Tertunday - field_assignable: Masalah dapat ditugaskan pada peran ini - field_redirect_existing_links: Alihkan tautan yang ada - field_estimated_hours: Perkiraan waktu - field_column_names: Kolom - field_time_zone: Zona waktu - field_searchable: Dapat dicari - field_default_value: Nilai default - field_comments_sorting: Tampilkan komentar - field_parent_title: Halaman induk - field_editable: Dapat disunting - field_watcher: Pemantau - field_identity_url: OpenID URL - field_content: Isi - field_group_by: Dikelompokkan berdasar - field_sharing: Berbagi - - setting_app_title: Judul aplikasi - setting_app_subtitle: Subjudul aplikasi - setting_welcome_text: Teks sambutan - setting_default_language: Bahasa Default - setting_login_required: Butuhkan otentikasi - setting_self_registration: Swa-pendaftaran - setting_attachment_max_size: Ukuran maksimum untuk lampiran - setting_issues_export_limit: Batasan ukuran export masalah - setting_mail_from: Emisi alamat email - setting_bcc_recipients: Blind carbon copy recipients (bcc) - setting_plain_text_mail: Plain text mail (no HTML) - setting_host_name: Nama host dan path - setting_text_formatting: Format teks - setting_wiki_compression: Kompresi untuk riwayat wiki - setting_feeds_limit: Batasan isi feed - setting_default_projects_public: Proyek baru defaultnya adalah publik - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Aktifkan WS untuk pengaturan repositori - setting_commit_ref_keywords: Referensi kaca kunci - setting_commit_fix_keywords: Pembetulan kaca kunci - setting_autologin: Autologin - setting_date_format: Format tanggal - setting_time_format: Format waktu - setting_cross_project_issue_relations: Perbolehkan kaitan masalah proyek berbeda - setting_issue_list_default_columns: Kolom default ditampilkan di daftar masalah - setting_emails_footer: Footer untuk email - setting_protocol: Protokol - setting_per_page_options: Pilihan obyek per halaman - setting_user_format: Format tampilan untuk pengguna - setting_activity_days_default: Hari tertampil pada kegiatan proyek - setting_display_subprojects_issues: Secara default, tampilkan masalah subproyek pada proyek utama - setting_enabled_scm: Enabled SCM - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - setting_sequential_project_identifiers: Buat pengenal proyek terurut - setting_gravatar_enabled: Gunakan icon pengguna dari Gravatar - setting_gravatar_default: Gambar default untuk Gravatar - setting_diff_max_lines_displayed: Maksimum perbedaan baris tertampil - setting_file_max_size_displayed: Maksimum berkas tertampil secara inline - setting_repository_log_display_limit: Nilai maksimum dari revisi ditampilkan di log berkas - setting_openid: Perbolehkan Login dan pendaftaran melalui OpenID - setting_password_min_length: Panjang minimum untuk kata sandi - setting_new_project_user_role_id: Peran diberikan pada pengguna non-admin yang membuat proyek - setting_default_projects_modules: Modul yang diaktifkan pada proyek baru - - permission_add_project: Tambahkan proyek - permission_edit_project: Sunting proyek - permission_select_project_modules: Pilih modul proyek - permission_manage_members: Atur anggota - permission_manage_versions: Atur versi - permission_manage_categories: Atur kategori masalah - permission_add_issues: Tambahkan masalah - permission_edit_issues: Sunting masalah - permission_manage_issue_relations: Atur kaitan masalah - permission_add_issue_notes: Tambahkan catatan - permission_edit_issue_notes: Sunting catatan - permission_edit_own_issue_notes: Sunting catatan saya - permission_move_issues: Pindahkan masalah - permission_delete_issues: Hapus masalah - permission_manage_public_queries: Atur query publik - permission_save_queries: Simpan query - permission_view_gantt: Tampilkan gantt chart - permission_view_calendar: Tampilkan kalender - permission_view_issue_watchers: Tampilkan daftar pemantau - permission_add_issue_watchers: Tambahkan pemantau - permission_delete_issue_watchers: Hapus pemantau - permission_log_time: Log waktu terpakai - permission_view_time_entries: Tampilkan waktu terpakai - permission_edit_time_entries: Sunting catatan waktu - 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 - permission_manage_wiki: Atur wiki - permission_rename_wiki_pages: Ganti nama halaman wiki - permission_delete_wiki_pages: Hapus halaman wiki - permission_view_wiki_pages: Tampilkan wiki - permission_view_wiki_edits: Tampilkan riwayat wiki - permission_edit_wiki_pages: Sunting halaman wiki - permission_delete_wiki_pages_attachments: Hapus lampiran - permission_protect_wiki_pages: Proteksi halaman wiki - permission_manage_repository: Atur repositori - permission_browse_repository: Jelajah repositori - permission_view_changesets: Tampilkan set perubahan - permission_commit_access: Commit akses - permission_manage_boards: Atur forum - permission_view_messages: Tampilkan pesan - permission_add_messages: Tambahkan pesan - permission_edit_messages: Sunting pesan - permission_edit_own_messages: Sunting pesan saya - permission_delete_messages: Hapus pesan - permission_delete_own_messages: Hapus pesan saya - - project_module_issue_tracking: Pelacak masalah - project_module_time_tracking: Pelacak waktu - project_module_news: Berita - project_module_documents: Dokumen - project_module_files: Berkas - project_module_wiki: Wiki - project_module_repository: Repositori - project_module_boards: Forum - - label_user: Pengguna - label_user_plural: Pengguna - label_user_new: Pengguna baru - label_user_anonymous: Anonymous - label_project: Proyek - label_project_new: Proyek baru - label_project_plural: Proyek - label_x_projects: - zero: tidak ada proyek - one: 1 proyek - other: "%{count} proyek" - label_project_all: Semua Proyek - label_project_latest: Proyek terakhir - label_issue: Masalah - label_issue_new: Masalah baru - label_issue_plural: Masalah - label_issue_view_all: tampilkan semua masalah - label_issues_by: "Masalah ditambahkan oleh %{value}" - label_issue_added: Masalah ditambahan - label_issue_updated: Masalah diperbarui - label_document: Dokumen - label_document_new: Dokumen baru - label_document_plural: Dokumen - label_document_added: Dokumen ditambahkan - label_role: Peran - label_role_plural: Peran - label_role_new: Peran baru - label_role_and_permissions: Peran dan perijinan - label_member: Anggota - label_member_new: Anggota baru - label_member_plural: Anggota - label_tracker: Pelacak - label_tracker_plural: Pelacak - label_tracker_new: Pelacak baru - label_workflow: Alur kerja - label_issue_status: Status masalah - label_issue_status_plural: Status masalah - label_issue_status_new: Status baru - label_issue_category: Kategori masalah - label_issue_category_plural: Kategori masalah - label_issue_category_new: Kategori baru - label_custom_field: Field kustom - label_custom_field_plural: Field kustom - label_custom_field_new: Field kustom - label_enumerations: Enumerasi - label_enumeration_new: Buat baru - label_information: Informasi - label_information_plural: Informasi - label_please_login: Silakan login - label_register: mendaftar - label_login_with_open_id_option: atau login menggunakan OpenID - label_password_lost: Lupa password - label_home: Halaman depan - label_my_page: Beranda - label_my_account: Akun saya - label_my_projects: Proyek saya - label_administration: Administrasi - label_login: Login - label_logout: Keluar - label_help: Bantuan - label_reported_issues: Masalah terlapor - label_assigned_to_me_issues: Masalah yang ditugaskan pada saya - label_last_login: Terakhir login - label_registered_on: Terdaftar pada - label_activity: Kegiatan - label_overall_activity: Kegiatan umum - label_user_activity: "kegiatan %{value}" - label_new: Baru - label_logged_as: Login sebagai - label_environment: Lingkungan - label_authentication: Otentikasi - label_auth_source: Mode Otentikasi - label_auth_source_new: Mode otentikasi baru - label_auth_source_plural: Mode Otentikasi - label_subproject_plural: Subproyek - label_and_its_subprojects: "%{value} dan subproyeknya" - label_min_max_length: Panjang Min - Maks - label_list: Daftar - label_date: Tanggal - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - 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 - label_attachment: Berkas - label_attachment_new: Berkas baru - label_attachment_delete: Hapus Berkas - label_attachment_plural: Berkas - label_file_added: Berkas ditambahkan - label_report: Laporan - label_report_plural: Laporan - label_news: Berita - label_news_new: Tambahkan berita - label_news_plural: Berita - label_news_latest: Berita terakhir - label_news_view_all: Tampilkan semua berita - label_news_added: Berita ditambahkan - label_settings: Pengaturan - label_overview: Umum - label_version: Versi - label_version_new: Versi baru - label_version_plural: Versi - label_confirmation: Konfirmasi - label_export_to: 'Juga tersedia dalam:' - label_read: Baca... - label_public_projects: Proyek publik - label_open_issues: belum selesai - label_open_issues_plural: belum selesai - label_closed_issues: selesai - label_closed_issues_plural: selesai - label_x_open_issues_abbr_on_total: - zero: 0 belum selesai / %{total} - one: 1 belum selesai / %{total} - other: "%{count} terbuka / %{total}" - label_x_open_issues_abbr: - zero: 0 belum selesai - one: 1 belum selesai - other: "%{count} belum selesai" - label_x_closed_issues_abbr: - zero: 0 selesai - one: 1 selesai - other: "%{count} selesai" - label_total: Total - label_permissions: Perijinan - label_current_status: Status sekarang - label_new_statuses_allowed: Status baru yang diijinkan - label_all: semua - label_none: tidak ada - label_nobody: tidak ada - label_next: Berikut - label_previous: Sebelum - label_used_by: Digunakan oleh - label_details: Rincian - label_add_note: Tambahkan catatan - label_per_page: Per halaman - label_calendar: Kalender - label_months_from: dari bulan - label_gantt: Gantt - label_internal: Internal - label_last_changes: "%{count} perubahan terakhir" - label_change_view_all: Tampilkan semua perubahan - label_personalize_page: Personalkan halaman ini - label_comment: Komentar - label_comment_plural: Komentar - label_x_comments: - zero: tak ada komentar - one: 1 komentar - other: "%{count} komentar" - label_comment_add: Tambahkan komentar - label_comment_added: Komentar ditambahkan - label_comment_delete: Hapus komentar - label_query: Custom query - label_query_plural: Custom queries - label_query_new: Query baru - label_filter_add: Tambahkan filter - label_filter_plural: Filter - label_equals: sama dengan - label_not_equals: tidak sama dengan - label_in_less_than: kurang dari - label_in_more_than: lebih dari - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: pada - label_today: hari ini - label_all_time: semua waktu - label_yesterday: kemarin - label_this_week: minggu ini - label_last_week: minggu lalu - label_last_n_days: "%{count} hari terakhir" - label_this_month: bulan ini - label_last_month: bulan lalu - label_this_year: this year - label_date_range: Jangkauan tanggal - label_less_than_ago: kurang dari hari yang lalu - label_more_than_ago: lebih dari hari yang lalu - label_ago: hari yang lalu - label_contains: berisi - label_not_contains: tidak berisi - label_day_plural: hari - 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 - label_revision_plural: Revisi - label_associated_revisions: Revisi terkait - label_added: ditambahkan - label_modified: diubah - label_copied: disalin - label_renamed: diganti nama - label_deleted: dihapus - label_latest_revision: Revisi terakhir - label_latest_revision_plural: Revisi terakhir - label_view_revisions: Tampilkan revisi - label_view_all_revisions: Tampilkan semua revisi - label_max_size: Ukuran maksimum - label_sort_highest: Ke paling atas - label_sort_higher: Ke atas - label_sort_lower: Ke bawah - label_sort_lowest: Ke paling bawah - label_roadmap: Rencana kerja - label_roadmap_due_in: "Harus selesai dalam %{value}" - label_roadmap_overdue: "%{value} terlambat" - label_roadmap_no_issues: Tak ada masalah pada versi ini - label_search: Cari - label_result_plural: Hasil - label_all_words: Semua kata - label_wiki: Wiki - label_wiki_edit: Sunting wiki - label_wiki_edit_plural: Sunting wiki - label_wiki_page: Halaman wiki - label_wiki_page_plural: Halaman wiki - label_index_by_title: Indeks menurut judul - label_index_by_date: Indeks menurut tanggal - label_current_version: Versi sekarang - label_preview: Tinjauan - label_feed_plural: Feeds - label_changes_details: Rincian semua perubahan - label_issue_tracking: Pelacak masalah - label_spent_time: Waktu terpakai - label_f_hour: "%{value} jam" - label_f_hour_plural: "%{value} jam" - label_time_tracking: Pelacak waktu - label_change_plural: Perubahan - label_statistics: Statistik - label_commits_per_month: Komit per bulan - label_commits_per_author: Komit per pengarang - label_view_diff: Tampilkan perbedaan - label_diff_inline: inline - label_diff_side_by_side: berdampingan - label_options: Pilihan - label_copy_workflow_from: Salin alur kerja dari - label_permissions_report: Laporan perijinan - label_watched_issues: Masalah terpantau - label_related_issues: Masalah terkait - label_applied_status: Status teraplikasi - label_loading: Memuat... - label_relation_new: Kaitan baru - label_relation_delete: Hapus kaitan - label_relates_to: terkait pada - label_duplicates: salinan - label_duplicated_by: disalin oleh - label_blocks: blok - label_blocked_by: diblok oleh - label_precedes: mendahului - label_follows: mengikuti - label_end_to_start: akhir ke awal - label_end_to_end: akhir ke akhir - label_start_to_start: awal ke awal - label_start_to_end: awal ke akhir - label_stay_logged_in: Tetap login - label_disabled: tidak diaktifkan - label_show_completed_versions: Tampilkan versi lengkap - label_me: saya - label_board: Forum - label_board_new: Forum baru - label_board_plural: Forum - label_topic_plural: Topik - label_message_plural: Pesan - label_message_last: Pesan terakhir - label_message_new: Pesan baru - label_message_posted: Pesan ditambahkan - label_reply_plural: Balasan - label_send_information: Kirim informasi akun ke pengguna - label_year: Tahun - label_month: Bulan - label_week: Minggu - label_date_from: Dari - label_date_to: Sampai - label_language_based: Berdasarkan bahasa pengguna - label_sort_by: "Urut berdasarkan %{value}" - label_send_test_email: Kirim email percobaan - label_feeds_access_key_created_on: "RSS access key dibuat %{value} yang lalu" - label_module_plural: Modul - label_added_time_by: "Ditambahkan oleh %{author} %{age} yang lalu" - label_updated_time_by: "Diperbarui oleh %{author} %{age} yang lalu" - label_updated_time: "Diperbarui oleh %{value} yang lalu" - label_jump_to_a_project: Pilih proyek... - label_file_plural: Berkas - label_changeset_plural: Set perubahan - label_default_columns: Kolom default - label_no_change_option: (Tak ada perubahan) - label_bulk_edit_selected_issues: Ubah masalah terpilih secara masal - label_theme: Tema - label_default: Default - label_search_titles_only: Cari judul saja - label_user_mail_option_all: "Untuk semua kejadian pada semua proyek saya" - label_user_mail_option_selected: "Hanya untuk semua kejadian pada proyek yang saya pilih ..." - label_user_mail_no_self_notified: "Saya tak ingin diberitahu untuk perubahan yang saya buat sendiri" - label_user_mail_assigned_only_mail_notification: "Kirim email hanya bila saya ditugaskan untuk masalah terkait" - label_user_mail_block_mail_notification: "Saya tidak ingin menerima email. Terima kasih." - label_registration_activation_by_email: aktivasi akun melalui email - label_registration_manual_activation: aktivasi akun secara manual - label_registration_automatic_activation: aktivasi akun secara otomatis - label_display_per_page: "Per halaman: %{value}" - label_age: Umur - label_change_properties: Rincian perubahan - label_general: Umum - label_more: Lanjut - label_scm: SCM - label_plugins: Plugin - label_ldap_authentication: Otentikasi LDAP - label_downloads_abbr: Unduh - label_optional_description: Deskripsi optional - label_add_another_file: Tambahkan berkas lain - label_preferences: Preferensi - label_chronological_order: Urut sesuai kronologis - label_reverse_chronological_order: Urut dari yang terbaru - label_planning: Perencanaan - label_incoming_emails: Email masuk - label_generate_key: Buat kunci - label_issue_watchers: Pemantau - label_example: Contoh - label_display: Tampilan - label_sort: Urut - label_ascending: Menaik - label_descending: Menurun - label_date_from_to: Dari %{start} sampai %{end} - label_wiki_content_added: Halaman wiki ditambahkan - label_wiki_content_updated: Halaman wiki diperbarui - label_group: Kelompok - label_group_plural: Kelompok - label_group_new: Kelompok baru - label_time_entry_plural: Waktu terpakai - label_version_sharing_none: Tidak dibagi - label_version_sharing_descendants: Dengan subproyek - label_version_sharing_hierarchy: Dengan hirarki proyek - label_version_sharing_tree: Dengan pohon proyek - label_version_sharing_system: Dengan semua proyek - - - button_login: Login - button_submit: Kirim - button_save: Simpan - button_check_all: Contreng semua - button_uncheck_all: Hilangkan semua contreng - button_delete: Hapus - button_create: Buat - button_create_and_continue: Buat dan lanjutkan - button_test: Test - button_edit: Sunting - button_add: Tambahkan - button_change: Ubah - button_apply: Terapkan - button_clear: Bersihkan - button_lock: Kunci - button_unlock: Buka kunci - button_download: Unduh - button_list: Daftar - button_view: Tampilkan - button_move: Pindah - button_move_and_follow: Pindah dan ikuti - button_back: Kembali - button_cancel: Batal - button_activate: Aktifkan - button_sort: Urut - button_log_time: Rekam waktu - button_rollback: Kembali ke versi ini - button_watch: Pantau - button_unwatch: Tidak Memantau - button_reply: Balas - button_archive: Arsip - button_unarchive: Batalkan arsip - button_reset: Reset - button_rename: Ganti nama - button_change_password: Ubah kata sandi - button_copy: Salin - button_copy_and_follow: Salin dan ikuti - button_annotate: Anotasi - button_update: Perbarui - button_configure: Konfigur - button_quote: Kutip - button_duplicate: Duplikat - - status_active: aktif - status_registered: terdaftar - status_locked: terkunci - - version_status_open: terbuka - version_status_locked: terkunci - version_status_closed: tertutup - - field_active: Aktif - - text_select_mail_notifications: Pilih aksi dimana email notifikasi akan dikirimkan. - text_regexp_info: mis. ^[A-Z0-9]+$ - text_min_max_length_info: 0 berarti tidak ada pembatasan - text_project_destroy_confirmation: Apakah anda benar-benar akan menghapus proyek ini beserta data terkait ? - text_subprojects_destroy_warning: "Subproyek: %{value} juga akan dihapus." - text_workflow_edit: Pilih peran dan pelacak untuk menyunting alur kerja - text_are_you_sure: Anda yakin ? - text_journal_changed: "%{label} berubah dari %{old} menjadi %{new}" - text_journal_set_to: "%{label} di set ke %{value}" - text_journal_deleted: "%{label} dihapus (%{old})" - text_journal_added: "%{label} %{value} ditambahkan" - text_tip_issue_begin_day: tugas dimulai hari itu - text_tip_issue_end_day: tugas berakhir hari itu - text_tip_issue_begin_end_day: tugas dimulai dan berakhir hari itu - text_caracters_maximum: "maximum %{count} karakter." - text_caracters_minimum: "Setidaknya harus sepanjang %{count} karakter." - text_length_between: "Panjang diantara %{min} dan %{max} karakter." - text_tracker_no_workflow: Tidak ada alur kerja untuk pelacak ini - text_unallowed_characters: Karakter tidak diperbolehkan - text_comma_separated: Beberapa nilai diperbolehkan (dipisahkan koma). - text_issues_ref_in_commit_messages: Mereferensikan dan membetulkan masalah pada pesan komit - text_issue_added: "Masalah %{id} sudah dilaporkan oleh %{author}." - text_issue_updated: "Masalah %{id} sudah diperbarui oleh %{author}." - text_wiki_destroy_confirmation: Apakah anda benar-benar akan menghapus wiki ini beserta semua isinya ? - text_issue_category_destroy_question: "Beberapa masalah (%{count}) ditugaskan pada kategori ini. Apa yang anda lakukan ?" - text_issue_category_destroy_assignments: Hapus kategori penugasan - text_issue_category_reassign_to: Tugaskan kembali masalah untuk kategori ini - text_user_mail_option: "Untuk proyek yang tidak dipilih, anda hanya akan menerima notifikasi hal-hal yang anda pantau atau anda terlibat di dalamnya (misalnya masalah yang anda tulis atau ditugaskan pada anda)." - text_no_configuration_data: "Peran, pelacak, status masalah dan alur kerja belum dikonfigur.\nSangat disarankan untuk memuat konfigurasi default. Anda akan bisa mengubahnya setelah konfigurasi dimuat." - text_load_default_configuration: Muat konfigurasi default - text_status_changed_by_changeset: "Diterapkan di set perubahan %{value}." - text_issues_destroy_confirmation: 'Apakah anda yakin untuk menghapus masalah terpilih ?' - text_select_project_modules: 'Pilih modul untuk diaktifkan pada proyek ini:' - text_default_administrator_account_changed: Akun administrator default sudah berubah - text_file_repository_writable: Direktori yang bisa ditulisi untuk lampiran - text_plugin_assets_writable: Direktori yang bisa ditulisi untuk plugin asset - text_rmagick_available: RMagick tersedia (optional) - text_destroy_time_entries_question: "%{hours} jam sudah dilaporkan pada masalah yang akan anda hapus. Apa yang akan anda lakukan ?" - text_destroy_time_entries: Hapus jam yang terlapor - text_assign_time_entries_to_project: Tugaskan jam terlapor pada proyek - text_reassign_time_entries: 'Tugaskan kembali jam terlapor pada masalah ini:' - text_user_wrote: "%{value} menulis:" - text_enumeration_destroy_question: "%{count} obyek ditugaskan untuk nilai ini." - text_enumeration_category_reassign_to: 'Tugaskan kembali untuk nilai ini:' - text_email_delivery_not_configured: "Pengiriman email belum dikonfigurasi, notifikasi tidak diaktifkan.\nAnda harus mengkonfigur SMTP server anda pada config/configuration.yml dan restart kembali aplikasi untuk mengaktifkan." - text_repository_usernames_mapping: "Pilih atau perbarui pengguna Redmine yang terpetakan ke setiap nama pengguna yang ditemukan di log repositori.\nPengguna dengan nama pengguna dan repositori atau email yang sama secara otomasit akan dipetakan." - text_diff_truncated: '... Perbedaan terpotong karena melebihi batas maksimum yang bisa ditampilkan.' - text_custom_field_possible_values_info: 'Satu baris untuk setiap nilai' - text_wiki_page_destroy_question: "Halaman ini mempunyai %{descendants} halaman anak dan turunannya. Apa yang akan anda lakukan ?" - text_wiki_page_nullify_children: "Biarkan halaman anak sebagai halaman teratas (root)" - text_wiki_page_destroy_children: "Hapus halaman anak dan semua turunannya" - text_wiki_page_reassign_children: "Tujukan halaman anak ke halaman induk yang ini" - - default_role_manager: Manager - default_role_developer: Pengembang - default_role_reporter: Pelapor - default_tracker_bug: Bug - default_tracker_feature: Fitur - default_tracker_support: Dukungan - default_issue_status_new: Baru - default_issue_status_in_progress: Dalam proses - default_issue_status_resolved: Resolved - default_issue_status_feedback: Umpan balik - default_issue_status_closed: Ditutup - default_issue_status_rejected: Ditolak - default_doc_category_user: Dokumentasi pengguna - default_doc_category_tech: Dokumentasi teknis - default_priority_low: Rendah - default_priority_normal: Normal - default_priority_high: Tinggi - default_priority_urgent: Penting - default_priority_immediate: Segera - default_activity_design: Rancangan - default_activity_development: Pengembangan - - enumeration_issue_priorities: Prioritas masalah - enumeration_doc_categories: Kategori dokumen - enumeration_activities: Kegiatan - enumeration_system_activity: Kegiatan Sistem - label_copy_source: Source - label_update_issue_done_ratios: Update issue done ratios - setting_issue_done_ratio: Calculate the issue done ratio with - label_api_access_key: API access key - text_line_separated: Multiple values allowed (one line for each value). - label_revision_id: Revision %{value} - permission_view_issues: View Issues - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - label_display_used_statuses_only: Only display statuses that are used by this tracker - error_workflow_copy_target: Please select target tracker(s) and role(s) - 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_copy_same_as_target: Same as target - button_show: Show - setting_issue_done_ratio_issue_field: Use the issue field - label_missing_api_access_key: Missing an API access key - label_copy_target: Target - label_missing_feeds_access_key: Missing a RSS access key - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - setting_start_of_week: Start calendars on - 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_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}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 masalah - one: 1 masalah - other: "%{count} masalah" - 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: semua - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks - label_cross_project_descendants: Dengan subproyek - label_cross_project_tree: Dengan pohon proyek - label_cross_project_hierarchy: Dengan hirarki proyek - label_cross_project_system: Dengan semua proyek - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/f8/f8fd519cf78697e49b186dd8e07016833f520d2f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/f8/f8fd519cf78697e49b186dd8e07016833f520d2f.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,49 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/f9/f922d7c0c922dddd808045035ad0f654dfb46fda.svn-base --- a/.svn/pristine/f9/f922d7c0c922dddd808045035ad0f654dfb46fda.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Document < ActiveRecord::Base - 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_searchable :columns => ['title', "#{table_name}.description"], :include => :project - acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, - :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil }, - :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => :project} - - validates_presence_of :project, :title, :category - validates_length_of :title, :maximum => 60 - - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } } - - safe_attributes 'category_id', 'title', 'description' - - def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_documents, project) - end - - def initialize(attributes=nil, *args) - super - if new_record? - self.category ||= DocumentCategory.default - end - end - - def updated_on - unless @updated_on - a = attachments.last - @updated_on = (a && a.created_on) || created_on - end - @updated_on - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/f9/f924ef380c43b9bc6870c2880f294487b8ad3aa8.svn-base --- a/.svn/pristine/f9/f924ef380c43b9bc6870c2880f294487b8ad3aa8.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 Configuration - - # Configuration default values - @defaults = { - 'email_delivery' => nil - } - - @config = nil - - class << self - # Loads the Redmine configuration file - # Valid options: - # * :file: the configuration file to load (default: config/configuration.yml) - # * :env: the environment to load the configuration for (default: Rails.env) - def load(options={}) - filename = options[:file] || File.join(Rails.root, 'config', 'configuration.yml') - env = options[:env] || Rails.env - - @config = @defaults.dup - - load_deprecated_email_configuration(env) - if File.file?(filename) - @config.merge!(load_from_yaml(filename, env)) - end - - # Compatibility mode for those who copy email.yml over configuration.yml - %w(delivery_method smtp_settings sendmail_settings).each do |key| - if value = @config.delete(key) - @config['email_delivery'] ||= {} - @config['email_delivery'][key] = value - end - end - - if @config['email_delivery'] - ActionMailer::Base.perform_deliveries = true - @config['email_delivery'].each do |k, v| - v.symbolize_keys! if v.respond_to?(:symbolize_keys!) - ActionMailer::Base.send("#{k}=", v) - end - end - - @config - end - - # Returns a configuration setting - def [](name) - load unless @config - @config[name] - end - - # Yields a block with the specified hash configuration settings - def with(settings) - settings.stringify_keys! - load unless @config - was = settings.keys.inject({}) {|h,v| h[v] = @config[v]; h} - @config.merge! settings - yield if block_given? - @config.merge! was - end - - private - - def load_from_yaml(filename, env) - yaml = nil - begin - yaml = YAML::load_file(filename) - rescue ArgumentError - $stderr.puts "Your Redmine configuration file located at #{filename} is not a valid YAML file and could not be loaded." - exit 1 - end - conf = {} - if yaml.is_a?(Hash) - if yaml['default'] - conf.merge!(yaml['default']) - end - if yaml[env] - conf.merge!(yaml[env]) - end - else - $stderr.puts "Your Redmine configuration file located at #{filename} is not a valid Redmine configuration file." - exit 1 - end - conf - end - - def load_deprecated_email_configuration(env) - deprecated_email_conf = File.join(Rails.root, 'config', 'email.yml') - if File.file?(deprecated_email_conf) - warn "Storing outgoing emails configuration in config/email.yml is deprecated. You should now store it in config/configuration.yml using the email_delivery setting." - @config.merge!({'email_delivery' => load_from_yaml(deprecated_email_conf, env)}) - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/f9/f997cdb8d5aa036fade9b17162bf4429d398a59e.svn-base --- a/.svn/pristine/f9/f997cdb8d5aa036fade9b17162bf4429d398a59e.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -<%= link_to(@repository.identifier.present? ? h(@repository.identifier) : 'root', - :action => 'show', :id => @project, - :repository_id => @repository.identifier_param, - :path => nil, :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, :repository_id => @repository.identifier_param, - :path => to_path_param(link_path), :rev => @rev %> -<% end %> -<% if filename %> - / <%= link_to h(filename), - :action => 'changes', :id => @project, :repository_id => @repository.identifier_param, - :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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fa16a1d3cc85f7d975c8b277b6a1fb967cd6fdd6.svn-base --- a/.svn/pristine/fa/fa16a1d3cc85f7d975c8b277b6a1fb967cd6fdd6.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1208 +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 - - if text.sub!( /\{([^"}]*)\}/, '' ) && !filter_styles - sanitized = sanitize_styles($1) - style << "#{ sanitized };" unless sanitized.blank? - end - - 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 - - STYLES_RE = /^(color|width|height|border|background|padding|margin|font|text|float)(-[a-z]+)*:\s*((\d+%?|\d+px|\d+(\.\d+)?em|#[0-9a-f]+|[a-z]+)\s*)+$/i - - def sanitize_styles(str) - styles = str.split(";").map(&:strip) - styles.reject! do |style| - !style.match(STYLES_RE) - end - styles.join(";") - 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 - while v = depth.pop - 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 - [[:alnum:]_\/]\S+? - ) - (\/)? # $slash - ([^[:alnum:]_\=\/;\(\)]*?) # $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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fa1ed3a060024a3911e7dcef012f4c68185e0ea3.svn-base --- a/.svn/pristine/fa/fa1ed3a060024a3911e7dcef012f4c68185e0ea3.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -# 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 BoardTest < ActiveSupport::TestCase - fixtures :projects, :boards, :messages, :attachments, :watchers - - include Redmine::I18n - - def setup - @project = Project.find(1) - end - - def test_create - board = Board.new(:project => @project, :name => 'Test board', :description => 'Test board description') - assert board.save - board.reload - assert_equal 'Test board', board.name - assert_equal 'Test board description', board.description - assert_equal @project, board.project - assert_equal 0, board.topics_count - assert_equal 0, board.messages_count - assert_nil board.last_message - # last position - assert_equal @project.boards.size, board.position - end - - def test_parent_should_be_in_same_project - set_language_if_valid 'en' - board = Board.new(:project_id => 3, :name => 'Test', :description => 'Test', :parent_id => 1) - assert !board.save - assert_include "Parent forum is invalid", board.errors.full_messages - end - - def test_valid_parents_should_not_include_self_nor_a_descendant - board1 = Board.generate!(:project_id => 3) - board2 = Board.generate!(:project_id => 3, :parent => board1) - board3 = Board.generate!(:project_id => 3, :parent => board2) - board4 = Board.generate!(:project_id => 3) - - assert_equal [board4], board1.reload.valid_parents.sort_by(&:id) - assert_equal [board1, board4], board2.reload.valid_parents.sort_by(&:id) - assert_equal [board1, board2, board4], board3.reload.valid_parents.sort_by(&:id) - assert_equal [board1, board2, board3], board4.reload.valid_parents.sort_by(&:id) - end - - def test_position_should_be_assigned_with_parent_scope - parent1 = Board.generate!(:project_id => 3) - parent2 = Board.generate!(:project_id => 3) - child1 = Board.generate!(:project_id => 3, :parent => parent1) - child2 = Board.generate!(:project_id => 3, :parent => parent1) - - assert_equal 1, parent1.reload.position - assert_equal 1, child1.reload.position - assert_equal 2, child2.reload.position - assert_equal 2, parent2.reload.position - end - - def test_board_tree_should_yield_boards_with_level - parent1 = Board.generate!(:project_id => 3) - parent2 = Board.generate!(:project_id => 3) - child1 = Board.generate!(:project_id => 3, :parent => parent1) - child2 = Board.generate!(:project_id => 3, :parent => parent1) - child3 = Board.generate!(:project_id => 3, :parent => child1) - - tree = Board.board_tree(Project.find(3).boards) - - assert_equal [ - [parent1, 0], - [child1, 1], - [child3, 2], - [child2, 1], - [parent2, 0] - ], tree - end - - def test_destroy - board = Board.find(1) - assert_difference 'Message.count', -6 do - assert_difference 'Attachment.count', -1 do - assert_difference 'Watcher.count', -1 do - assert board.destroy - end - end - end - assert_equal 0, Message.count(:conditions => {:board_id => 1}) - end - - def test_destroy_should_nullify_children - parent = Board.generate!(:project => @project) - child = Board.generate!(:project => @project, :parent => parent) - assert_equal parent, child.parent - - assert parent.destroy - child.reload - assert_nil child.parent - assert_nil child.parent_id - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fa3b4d93c72c3ec159cca814b171bb7f0448ff0f.svn-base --- a/.svn/pristine/fa/fa3b4d93c72c3ec159cca814b171bb7f0448ff0f.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fa4c6495ebff9e1214dc90dd2c21d6f78f2cf2e7.svn-base --- a/.svn/pristine/fa/fa4c6495ebff9e1214dc90dd2c21d6f78f2cf2e7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 - @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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fa4e79285c00e0ec28562fe13c5b5fad98f92052.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fa/fa4e79285c00e0ec28562fe13c5b5fad98f92052.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,95 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fa/fae038378d362215d4026bc12ab909502d936808.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fa/fae038378d362215d4026bc12ab909502d936808.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fb44c6e118ba1da9fa2fdfb5c015150a0c9a67d4.svn-base --- a/.svn/pristine/fb/fb44c6e118ba1da9fa2fdfb5c015150a0c9a67d4.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1244 +0,0 @@ -# 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 'forwardable' -require 'cgi' - -module ApplicationHelper - 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 - - # Return true if user is authorized for controller/action, otherwise false - def authorize_for(controller, action) - User.current.allowed_to?({:controller => controller, :action => action}, @project) - end - - # Display a link if user is authorized - # - # @param [String] name Anchor text (passed to link_to) - # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized - # @param [optional, Hash] html_options Options passed to link_to - # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to - def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) - link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) - end - - # Displays a link to user's account page if active - def link_to_user(user, options={}) - if user.is_a?(User) - name = h(user.name(options[:format])) - if user.active? || (User.current.admin? && user.logged?) - link_to name, user_path(user), :class => user.css_classes - else - name - end - else - h(user.to_s) - end - end - - # Displays a link to +issue+ with its subject. - # Examples: - # - # link_to_issue(issue) # => Defect #6: This is the subject - # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... - # link_to_issue(issue, :subject => false) # => Defect #6 - # link_to_issue(issue, :project => true) # => Foo - Defect #6 - # link_to_issue(issue, :subject => false, :tracker => false) # => #6 - # - def link_to_issue(issue, options={}) - title = nil - subject = nil - text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" - if options[:subject] == false - title = truncate(issue.subject, :length => 60) - else - subject = issue.subject - if options[:truncate] - subject = truncate(subject, :length => options[:truncate]) - end - end - s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title - s << h(": #{subject}") if subject - s = h("#{issue.project} - ") + s if options[:project] - s - end - - # Generates a link to an attachment. - # Options: - # * :text - Link text (default to attachment filename) - # * :download - Force download (default: false) - def link_to_attachment(attachment, options={}) - text = options.delete(:text) || attachment.filename - 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 - # Options: - # * :text - Link text (default to the formatted revision) - def link_to_revision(revision, repository, options={}) - if repository.is_a?(Project) - repository = repository.repository - end - text = options.delete(:text) || format_revision(revision) - rev = revision.respond_to?(:identifier) ? revision.identifier : revision - link_to( - h(text), - {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, - :title => l(:label_revision_id, format_revision(revision)) - ) - end - - # Generates a link to a message - def link_to_message(message, options={}, html_options = nil) - link_to( - 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)), - html_options - ) - end - - # Generates a link to a project if active - # Examples: - # - # link_to_project(project) # => link to the specified project overview - # 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.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 - 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 - - def wiki_page_path(page, options={}) - url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) - end - - def thumbnail_tag(attachment) - link_to image_tag(thumbnail_path(attachment)), - named_attachment_path(attachment, attachment.filename), - :title => attachment.filename - end - - def toggle_link(name, id, options={}) - onclick = "$('##{id}').toggle(); " - onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") - onclick << "return false;" - link_to(name, "#", :onclick => onclick) - end - - def image_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - tag(:input, html_options.merge({ - :type => "image", :src => image_path(name), - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) - end - - def format_activity_title(text) - h(truncate_single_line(text, :length => 100)) - end - - def format_activity_day(date) - date == User.current.today ? l(:label_today).titleize : format_date(date) - end - - def format_activity_description(text) - h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') - ).gsub(/[\r\n]+/, "
    ").html_safe - end - - def format_version_name(version) - if version.project == @project - h(version) - else - h("#{version.project} - #{version}") - end - end - - def due_date_distance_in_words(date) - if date - l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) - end - end - - # Renders a tree of projects as a nested set of unordered lists - # The given collection may be a subset of the whole project tree - # (eg. some intermediate nodes are private and can not be seen) - def render_project_nested_lists(projects) - s = '' - if projects.any? - ancestors = [] - original_project = @project - projects.sort_by(&:lft).each do |project| - # set the project environment to please macros. - @project = project - if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) - s << "
      \n" - else - ancestors.pop - s << "" - while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) - ancestors.pop - s << "
    \n" - end - end - classes = (ancestors.empty? ? 'root' : 'child') - s << "
  • " - s << h(block_given? ? yield(project) : project.name) - s << "
    \n" - ancestors << project - end - s << ("
  • \n" * ancestors.size) - @project = original_project - end - s.html_safe - end - - def render_page_hierarchy(pages, node=nil, options={}) - content = '' - if pages[node] - content << "
      \n" - pages[node].each do |page| - content << "
    • " - content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, - :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) - content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] - content << "
    • \n" - end - content << "
    \n" - end - content.html_safe - end - - # Renders flash messages - def render_flash_messages - s = '' - flash.each do |k,v| - s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") - end - s.html_safe - end - - # Renders tabs and their content - def render_tabs(tabs) - if tabs.any? - render :partial => 'common/tabs', :locals => {:tabs => tabs} - else - content_tag 'p', l(:label_no_data), :class => "nodata" - end - end - - # Renders the project quick-jump box - def render_project_jump_box - return unless User.current.logged? - projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq - if projects.any? - options = - ("" + - '').html_safe - - options << project_tree_options_for_select(projects, :selected => @project) do |p| - { :value => project_path(:id => p, :jump => current_menu_item) } - end - - select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') - end - end - - def project_tree_options_for_select(projects, options = {}) - s = '' - project_tree(projects) do |project, level| - name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe - tag_options = {:value => project.id} - if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) - tag_options[:selected] = 'selected' - else - tag_options[:selected] = nil - end - tag_options.merge!(yield(project)) if block_given? - s << content_tag('option', name_prefix + h(project), tag_options) - end - s.html_safe - end - - # Yields the given block for each project with its level in the tree - # - # Wrapper for Project#project_tree - def project_tree(projects, &block) - Project.project_tree(projects, &block) - end - - def principals_check_box_tags(name, principals) - s = '' - principals.each do |principal| - s << "\n" - end - s.html_safe - end - - # Returns a string for users/groups option tags - def principals_options_for_select(collection, selected=nil) - s = '' - if collection.include?(User.current) - s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) - end - groups = '' - collection.sort.each do |element| - selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) - (element.is_a?(Group) ? groups : s) << %() - end - unless groups.empty? - s << %(#{groups}) - end - s.html_safe - end - - # Options for the new membership projects combo-box - 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.to_a.include?(p)} - end - options - end - - # Truncates and returns the string as a single line - def truncate_single_line(string, *args) - truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') - end - - # Truncates at line break after 250 characters or options[:length] - def truncate_lines(string, options={}) - length = options[:length] || 250 - if string.to_s =~ /\A(.{#{length}}.*?)$/m - "#{$1}..." - else - string - end - end - - def anchor(text) - text.to_s.gsub(' ', '_') - end - - def html_hours(text) - text.gsub(%r{(\d+)\.(\d+)}, '\1.\2').html_safe - end - - def authoring(created, author, options={}) - l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe - end - - def time_tag(time) - text = distance_of_time_in_words(Time.now, time) - if @project - link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) - else - content_tag('acronym', text, :title => format_time(time)) - end - end - - def syntax_highlight_lines(name, content) - lines = [] - syntax_highlight(name, content).each_line { |line| lines << line } - lines - end - - def syntax_highlight(name, content) - Redmine::SyntaxHighlighting.highlight_by_filename(content, name) - end - - def to_path_param(path) - str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") - str.blank? ? nil : str - 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'}), - :method => method, :title => l(:label_sort_highest)) + - link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), - url.merge({"#{name}[move_to]" => 'higher'}), - :method => method, :title => l(:label_sort_higher)) + - link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), - url.merge({"#{name}[move_to]" => 'lower'}), - :method => method, :title => l(:label_sort_lower)) + - link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), - url.merge({"#{name}[move_to]" => 'lowest'}), - :method => method, :title => l(:label_sort_lowest)) - end - - def breadcrumb(*args) - elements = args.flatten - elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil - end - - def other_formats_links(&block) - concat('

    '.html_safe + l(:label_export_to)) - yield Redmine::Views::OtherFormatsBuilder.new(self) - concat('

    '.html_safe) - end - - def page_header_title - if @project.nil? || @project.new_record? - h(Setting.app_title) - else - b = [] - ancestors = (@project.root? ? [] : @project.ancestors.visible.all) - if ancestors.any? - root = ancestors.shift - b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') - if ancestors.size > 2 - b << "\xe2\x80\xa6" - ancestors = ancestors[-2, 2] - end - b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } - end - b << h(@project) - b.join(" \xc2\xbb ").html_safe - end - end - - def html_title(*args) - if args.empty? - title = @html_title || [] - title << @project.name if @project - title << Setting.app_title unless Setting.app_title == title.last - title.select {|t| !t.blank? }.join(' - ') - else - @html_title ||= [] - @html_title += args - end - end - - # Returns the theme, controller name, and action as css classes for the - # HTML body. - def body_css_classes - css = [] - if theme = Redmine::Themes.theme(Setting.ui_theme) - css << 'theme-' + theme.name - end - - css << 'controller-' + controller_name - css << 'action-' + action_name - css.join(' ') - end - - def accesskey(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. - # 2 ways to call this method: - # * with a String: textilizable(text, options) - # * with an object and one of its attribute: textilizable(issue, :description, options) - def textilizable(*args) - options = args.last.is_a?(Hash) ? args.pop : {} - case args.size - when 1 - obj = options[:object] - text = args.shift - when 2 - obj = args.shift - attr = args.shift - text = obj.send(attr).to_s - else - raise ArgumentError, 'invalid arguments to textilizable' - end - return '' if text.blank? - project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) - only_path = options.delete(:only_path) == false ? false : true - - text = text.dup - macros = catch_macros(text) - text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) - - @parsed_headings = [] - @heading_anchors = {} - @current_section = 0 if options[:edit_section_links] - - parse_sections(text, project, obj, attr, only_path, options) - text = parse_non_pre_blocks(text, obj, macros) do |text| - [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| - send method_name, text, project, obj, attr, only_path, options - end - end - parse_headings(text, project, obj, attr, only_path, options) - - if @parsed_headings.any? - replace_toc(text, @parsed_headings) - end - - text.html_safe - end - - def parse_non_pre_blocks(text, obj, macros) - s = StringScanner.new(text) - tags = [] - parsed = '' - while !s.eos? - s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) - text, full_tag, closing, tag = s[1], s[2], s[3], s[4] - if tags.empty? - yield text - inject_macros(text, obj, macros) if macros.any? - else - inject_macros(text, obj, macros, false) if macros.any? - end - parsed << text - if tag - if closing - if tags.last == tag.downcase - tags.pop - end - else - tags << tag.downcase - end - parsed << full_tag - end - end - # Close any non closing tags - while tag = tags.pop - parsed << "" - end - parsed - end - - def parse_inline_attachments(text, project, obj, attr, only_path, options) - # when using an image link, try to use an attachment, if possible - attachments = options[:attachments] || [] - attachments += obj.attachments if obj.respond_to?(:attachments) - if attachments.present? - text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| - filename, ext, alt, alttext = $1.downcase, $2, $3, $4 - # search for the picture in attachments - if found = Attachment.latest_attach(attachments, filename) - 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}\"" - end - "src=\"#{image_url}\"#{alt}" - else - m - end - end - end - end - - # Wiki links - # - # Examples: - # [[mypage]] - # [[mypage|mytext]] - # wiki links can refer other project wikis, using project name or identifier: - # [[project:]] -> wiki starting page - # [[project:|mytext]] - # [[project:mypage]] - # [[project:mypage|mytext]] - def parse_wiki_links(text, project, obj, attr, only_path, options) - text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| - link_project = project - esc, all, page, title = $1, $2, $3, $5 - if esc.nil? - if page =~ /^([^\:]+)\:(.*)$/ - 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 - # extract anchor - anchor = nil - if page =~ /^(.+?)\#(.+)$/ - page, anchor = $1, $2 - end - anchor = sanitize_anchor_name(anchor) if anchor.present? - # check if page exists - wiki_page = link_project.wiki.find_page(page) - url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page - "##{anchor}" - else - case options[:wiki_links] - when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') - when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export - else - wiki_page_id = page.present? ? Wiki.titleize(page) : nil - parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil - url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, - :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) - end - end - link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) - else - # project or wiki doesn't exist - all - end - else - all - end - end - end - - # Redmine links - # - # Examples: - # Issues: - # #52 -> Link to issue #52 - # Changesets: - # r52 -> Link to revision 52 - # commit:a85130f -> Link to scmid starting with a85130f - # Documents: - # document#17 -> Link to document with id 17 - # document:Greetings -> Link to the document with title "Greetings" - # document:"Some document" -> Link to the document with title "Some document" - # Versions: - # version#3 -> Link to version with id 3 - # version:1.0.0 -> Link to version named "1.0.0" - # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" - # Attachments: - # attachment:file.zip -> Link to the attachment of the current object named file.zip - # Source files: - # source:some/file -> Link to the file located at /some/file in the project's repository - # source:some/file@52 -> Link to the file's revision 52 - # source:some/file#L120 -> Link to line 120 of the file - # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 - # export:some/file -> Force the download of the file - # Forum messages: - # message#1218 -> Link to message with id 1218 - # - # Links can refer other objects from other projects, using project identifier: - # identifier:r52 - # identifier:document:"Some document" - # identifier:version:1.0.0 - # identifier:source:some/file - def parse_redmine_links(text, default_project, obj, attr, only_path, options) - text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| - leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 - link = nil - project = default_project - if project_identifier - project = Project.visible.find_by_identifier(project_identifier) - end - if esc.nil? - if prefix.nil? && sep == 'r' - if project - repository = nil - if repo_identifier - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - # project.changesets.visible raises an SQL error because of a double join on repositories - if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) - link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, - :class => 'changeset', - :title => truncate_single_line(changeset.comments, :length => 100)) - end - end - elsif sep == '#' - oid = identifier.to_i - case prefix - when nil - if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) - anchor = comment_id ? "note-#{comment_id}" : nil - link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, - :class => issue.css_classes, - :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") - end - when 'document' - if document = Document.visible.find_by_id(oid) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if version = Version.visible.find_by_id(oid) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'message' - if message = Message.visible.find_by_id(oid, :include => :parent) - link = link_to_message(message, {:only_path => only_path}, :class => 'message') - end - when 'forum' - if board = Board.visible.find_by_id(oid) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if news = News.visible.find_by_id(oid) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'project' - if p = Project.visible.find_by_id(oid) - link = link_to_project(p, {:only_path => only_path}, :class => 'project') - end - end - elsif sep == ':' - # removes the double quotes if any - name = identifier.gsub(%r{^"(.*)"$}, "\\1") - case prefix - when 'document' - if project && document = project.documents.visible.find_by_title(name) - link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, - :class => 'document' - end - when 'version' - if project && version = project.versions.visible.find_by_name(name) - link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, - :class => 'version' - end - when 'forum' - if project && board = project.boards.visible.find_by_name(name) - link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, - :class => 'board' - end - when 'news' - if project && news = project.news.visible.find_by_title(name) - link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, - :class => 'news' - end - when 'commit', 'source', 'export' - if project - repository = nil - if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$} - repo_prefix, repo_identifier, name = $1, $2, $3 - repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} - else - repository = project.repository - end - if prefix == 'commit' - 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(changeset.comments, :length => 100) - end - else - if repository && User.current.allowed_to?(:browse_repository, project) - 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), - :rev => rev, - :anchor => anchor}, - :class => (prefix == 'export' ? 'source download' : 'source') - end - end - repo_prefix = nil - end - when 'attachment' - attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) - if attachments && attachment = Attachment.latest_attach(attachments, name) - link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') - end - when 'project' - 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 - end - end - (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) - end - end - - HEADING_RE = /(]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) - - def parse_sections(text, project, obj, attr, only_path, options) - return unless options[:edit_section_links] - text.gsub!(HEADING_RE) do - heading = $1 - @current_section += 1 - if @current_section > 1 - content_tag('div', - link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), - :class => 'contextual', - :title => l(:button_edit_section)) + heading.html_safe - else - heading - end - end - end - - # Headings and TOC - # Adds ids and links to headings unless options[:headings] is set to false - def parse_headings(text, project, obj, attr, only_path, options) - return if options[:headings] == false - - text.gsub!(HEADING_RE) do - level, attrs, content = $2.to_i, $3, $4 - item = strip_tags(content).strip - anchor = sanitize_anchor_name(item) - # used for single-file wiki export - anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) - @heading_anchors[anchor] ||= 0 - idx = (@heading_anchors[anchor] += 1) - if idx > 1 - anchor = "#{anchor}-#{idx}" - end - @parsed_headings << [level, anchor, item] - "\n#{content}" - end - end - - MACROS_RE = /( - (!)? # escaping - ( - \{\{ # opening tag - ([\w]+) # macro name - (\(([^\n\r]*?)\))? # optional arguments - ([\n\r].*?[\n\r])? # optional block of text - \}\} # closing tag - ) - )/mx unless const_defined?(:MACROS_RE) - - MACRO_SUB_RE = /( - \{\{ - macro\((\d+)\) - \}\} - )/x unless const_defined?(:MACRO_SUB_RE) - - # Extracts macros from text - def catch_macros(text) - macros = {} - text.gsub!(MACROS_RE) do - all, macro = $1, $4.downcase - if macro_exists?(macro) || all =~ MACRO_SUB_RE - index = macros.size - macros[index] = all - "{{macro(#{index})}}" - else - all - end - end - macros - end - - # Executes and replaces macros in text - def inject_macros(text, obj, macros, execute=true) - text.gsub!(MACRO_SUB_RE) do - all, index = $1, $2.to_i - orig = macros.delete(index) - if execute && orig && orig =~ MACROS_RE - esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) - if esc.nil? - h(exec_macro(macro, obj, args, block) || all) - else - h(all) - end - elsif orig - h(orig) - else - h(all) - end - end - end - - TOC_RE = /

    \{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) - - # Renders the TOC with given headings - def replace_toc(text, headings) - text.gsub!(TOC_RE) do - # Keep only the 4 first levels - headings = headings.select{|level, anchor, item| level <= 4} - if headings.empty? - '' - else - div_class = 'toc' - div_class << ' right' if $1 == '>' - div_class << ' left' if $1 == '<' - out = "

    • " - root = headings.map(&:first).min - current = root - started = false - headings.each do |level, anchor, item| - if level > current - out << '
      • ' * (level - current) - elsif level < current - out << "
      \n" * (current - level) + "
    • " - elsif started - out << '
    • ' - end - out << "#{item}" - current = level - started = true - end - out << '
    ' * (current - root) - out << '' - end - end - end - - # Same as Rails' simple_format helper without using paragraphs - def simple_format_without_paragraph(text) - text.to_s. - gsub(/\r\n?/, "\n"). # \r\n and \r -> \n - gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br - gsub(/([^\n]\n)(?=[^\n])/, '\1
    '). # 1 newline -> br - html_safe - end - - def lang_options_for_select(blank=true) - (blank ? [["(auto)", ""]] : []) + languages_options - end - - def label_tag_for(name, option_tags = nil, options = {}) - label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") - content_tag("label", label_text) - end - - def labelled_form_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - if args.first.is_a?(Symbol) - options.merge!(:as => args.shift) - end - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - form_for(*args, &proc) - end - - def labelled_fields_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) - fields_for(*args, &proc) - end - - def labelled_remote_form_for(*args, &proc) - ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) - form_for(*args, &proc) - end - - def error_messages_for(*objects) - html = "" - objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact - errors = objects.map {|o| o.errors.full_messages}.flatten - if errors.any? - html << "
      \n" - errors.each do |error| - html << "
    • #{h error}
    • \n" - end - html << "
    \n" - end - html.html_safe - end - - def delete_link(url, options={}) - options = { - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :class => 'icon icon-del' - }.merge(options) - - link_to l(:button_delete), url, options - end - - def preview_link(url, form, target='preview', options={}) - content_tag 'a', l(:label_preview), { - :href => "#", - :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, - :accesskey => accesskey(:preview) - }.merge(options) - end - - def link_to_function(name, function, html_options={}) - content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) - end - - # Helper to render JSON in views - def raw_json(arg) - arg.to_json.to_s.gsub('/', '\/').html_safe - end - - def back_url - url = params[:back_url] - if url.nil? && referer = request.env['HTTP_REFERER'] - url = CGI.unescape(referer.to_s) - end - url - end - - def back_url_hidden_field_tag - url = back_url - hidden_field_tag('back_url', url, :id => nil) unless url.blank? - end - - def check_all_links(form_name) - link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + - " | ".html_safe + - link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") - end - - def progress_bar(pcts, options={}) - pcts = [pcts, pcts] unless pcts.is_a?(Array) - pcts = pcts.collect(&:round) - pcts[1] = pcts[1] - pcts[0] - pcts << (100 - pcts[1] - pcts[0]) - width = options[:width] || '100px;' - legend = options[:legend] || '' - content_tag('table', - content_tag('tr', - (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + - (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 => 'percent').html_safe - end - - def checked_image(checked=true) - if checked - image_tag 'toggle_check.png' - end - end - - def context_menu(url) - unless @context_menu_included - content_for :header_tags do - javascript_include_tag('context_menu') + - stylesheet_link_tag('context_menu') - end - if l(:direction) == 'rtl' - content_for :header_tags do - stylesheet_link_tag('context_menu_rtl') - end - end - @context_menu_included = true - end - javascript_tag "contextMenuInit('#{ url_for(url) }')" - end - - def calendar_for(field_id) - include_calendar_headers_tags - javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") - end - - def include_calendar_headers_tags - unless @calendar_headers_tags_included - @calendar_headers_tags_included = true - content_for :header_tags do - start_of_week = Setting.start_of_week - start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? - # Redmine uses 1..7 (monday..sunday) in settings and locales - # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 - start_of_week = start_of_week.to_i % 7 - - tags = javascript_tag( - "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + - "showOn: 'button', buttonImageOnly: true, buttonImage: '" + - path_to_image('/images/calendar.png') + - "', 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") - end - tags - end - end - end - - # Overrides Rails' stylesheet_link_tag with themes and plugins support. - # Examples: - # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults - # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets - # - def stylesheet_link_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/stylesheets/#{source}" - elsif current_theme && current_theme.stylesheets.include?(source) - current_theme.stylesheet_path(source) - else - source - end - end - super sources, options - end - - # Overrides Rails' image_tag with themes and plugins support. - # Examples: - # image_tag('image.png') # => picks image.png from the current theme or defaults - # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets - # - def image_tag(source, options={}) - if plugin = options.delete(:plugin) - source = "/plugin_assets/#{plugin}/images/#{source}" - elsif current_theme && current_theme.images.include?(source) - source = current_theme.image_path(source) - end - super source, options - end - - # Overrides Rails' javascript_include_tag with plugins support - # Examples: - # javascript_include_tag('scripts') # => picks scripts.js from defaults - # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets - # - def javascript_include_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop : {} - if plugin = options.delete(:plugin) - sources = sources.map do |source| - if plugin - "/plugin_assets/#{plugin}/javascripts/#{source}" - else - source - end - end - end - super sources, options - end - - def content_for(name, content = nil, &block) - @has_content ||= {} - @has_content[name] = true - super(name, content, &block) - end - - def has_content?(name) - (@has_content && @has_content[name]) || false - end - - def sidebar_content? - has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? - end - - def view_layouts_base_sidebar_hook_response - @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) - end - - def email_delivery_enabled? - !!ActionMailer::Base.perform_deliveries - end - - # Returns the avatar image tag for the given +user+ if avatars are enabled - # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') - def avatar(user, options = { }) - if Setting.gravatar_enabled? - options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) - email = nil - if user.respond_to?(:mail) - email = user.mail - elsif user.to_s =~ %r{<(.+?)>} - email = $1 - end - return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil - else - '' - end - end - - def sanitize_anchor_name(anchor) - if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' - 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*)?}, '-') - end - end - - # Returns the javascript tags that are included in the html layout head - def javascript_heads - 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 - tags - end - - def favicon - "".html_safe - end - - def robot_exclusion_tag - ''.html_safe - end - - # Returns true if arg is expected in the API response - def include_in_api_response?(arg) - unless @included_in_api_response - param = params[:include] - @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') - @included_in_api_response.collect!(&:strip) - end - @included_in_api_response.include?(arg.to_s) - end - - # Returns options or nil if nometa param or X-Redmine-Nometa header - # was set in the request - def api_meta(options) - if params[:nometa].present? || request.headers['X-Redmine-Nometa'] - # compatibility mode for activeresource clients that raise - # an error when unserializing an array with attributes - nil - else - options - end - end - - private - - def wiki_helper - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper - return self - end - - def link_to_content_update(text, url_params = {}, html_options = {}) - link_to(text, url_params, html_options) - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fb831ceb031f77a301df0cd90da94f30274b8153.svn-base --- a/.svn/pristine/fb/fb831ceb031f77a301df0cd90da94f30274b8153.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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_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_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/edit?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/edit?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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fb9aed3d609c2f59022b54e8ad9664c84c259e08.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fb/fb9aed3d609c2f59022b54e8ad9664c84c259e08.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,106 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 IssuePriorityTest < ActiveSupport::TestCase + fixtures :enumerations, :issues + + def test_named_scope + assert_equal Enumeration.find_by_name('Normal'), Enumeration.named('normal').first + end + + def test_default_should_return_the_default_priority + assert_equal Enumeration.find_by_name('Normal'), IssuePriority.default + end + + def test_default_should_return_nil_when_no_default_priority + IssuePriority.update_all :is_default => 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fba522b2ee4403afb33ab25e55c53d41892d5e4b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fb/fba522b2ee4403afb33ab25e55c53d41892d5e4b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,124 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/bazaar_adapter' + +class Repository::Bazaar < Repository + attr_protected :root_url + validates_presence_of :url, :log_encoding + + 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::BazaarAdapter + end + + def self.scm_name + 'Bazaar' + end + + def entry(path=nil, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.entry(path, identifier) + end + + def cat(path, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.cat(path, identifier) + end + + def annotate(path, identifier=nil) + scm.bzr_path_encodig = log_encoding + scm.annotate(path, identifier) + end + + def diff(path, rev, rev_to) + scm.bzr_path_encodig = log_encoding + scm.diff(path, rev, rev_to) + end + + def entries(path=nil, identifier=nil) + scm.bzr_path_encodig = log_encoding + entries = scm.entries(path, identifier) + if entries + entries.each do |e| + next if e.lastrev.revision.blank? + # Set the filesize unless browsing a specific revision + if identifier.nil? && e.is_file? + full_path = File.join(root_url, e.path) + e.size = File.stat(full_path).size if File.file?(full_path) + end + c = Change. + includes(:changeset). + where("#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id). + order("#{Changeset.table_name}.revision DESC"). + first + if c + e.lastrev.identifier = c.changeset.revision + e.lastrev.name = c.changeset.revision + e.lastrev.author = c.changeset.committer + end + end + end + load_entries_changesets(entries) + entries + end + + def fetch_changesets + scm.bzr_path_encodig = log_encoding + 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) + transaction do + revisions.reverse_each do |revision| + changeset = Changeset.create(:repository => self, + :revision => revision.identifier, + :committer => revision.author, + :committed_on => revision.time, + :scmid => revision.scmid, + :comments => revision.message) + + revision.paths.each do |change| + Change.create(:changeset => changeset, + :action => change[:action], + :path => change[:path], + :revision => change[:revision]) + end + end + end unless revisions.nil? + identifier_from = identifier_to + 1 + end + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fbc68f3692f8e2cc3c5089591e7d79fb0fb3bac7.svn-base --- a/.svn/pristine/fb/fbc68f3692f8e2cc3c5089591e7d79fb0fb3bac7.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -<%= render :partial => 'action_menu' %> - -

    <%=l(:label_workflow)%>

    - -
    -
      -
    • <%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %>
    • -
    • <%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %>
    • -
    -
    - -

    <%=l(:text_workflow_edit)%>:

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

    - - - - - <%= submit_tag l(:button_edit), :name => nil %> - - <%= hidden_field_tag 'used_statuses_only', '0' %> - - -

    -<% end %> - -<% if @tracker && @role && @statuses.any? %> - <%= form_tag({}, :id => 'workflow_form' ) do %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= hidden_field_tag 'role_id', @role.id %> - <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> -
    - <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> - -
    - <%= l(:label_additional_workflow_transitions_for_author) %> -
    - <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %> -
    -
    - <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %> - -
    - <%= l(:label_additional_workflow_transitions_for_assignee) %> -
    - <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %> -
    -
    - <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %> -
    - <%= submit_tag l(:button_save) %> - <% end %> -<% end %> - -<% html_title(l(:label_workflow)) -%> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fbd9312384b9108653ac87420225c6ac2b8787f0.svn-base --- a/.svn/pristine/fb/fbd9312384b9108653ac87420225c6ac2b8787f0.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 MenuManagerTest < 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_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' - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_activity), - :attributes => { :href => '/projects/ecookbook/activity', - :class => 'activity' } } } - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => ll('fr', :label_issue_plural), - :attributes => { :href => '/projects/ecookbook/issues', - :class => 'issues selected' } } } - end - - def test_project_menu_with_additional_menu_items - Setting.default_language = 'en' - assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do - Redmine::MenuManager.map :project_menu do |menu| - menu.push :foo, { :controller => 'projects', :action => 'show' }, :caption => 'Foo' - menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity - menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar - end - - get 'projects/ecookbook' - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo', - :attributes => { :class => 'foo' } } } - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar', - :attributes => { :class => 'bar' } }, - :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } } - - assert_tag :div, :attributes => { :id => 'main-menu' }, - :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK', - :attributes => { :class => 'hello' } }, - :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } } - - # Remove the menu items - Redmine::MenuManager.map :project_menu do |menu| - menu.delete :foo - menu.delete :bar - menu.delete :hello - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fbda7b3c7252d9de68731cba763258954d380bbf.svn-base --- a/.svn/pristine/fb/fbda7b3c7252d9de68731cba763258954d380bbf.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 QueriesHelperTest < ActionView::TestCase - include QueriesHelper - 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_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 filter_count + 1, fo.size - assert_equal [], fo[0] - - expected_order = [ - "Status", - "Project", - "Tracker", - "Priority" - ] - assert_equal expected_order, (fo.map(&:first) & expected_order) - end - - def test_filters_options_should_be_ordered_with_custom_fields - set_language_if_valid 'en' - field = UserCustomField.create!( - :name => 'order test', :field_format => 'string', - :is_for_all => true, :is_filter => true - ) - query = IssueQuery.new - filter_count = query.available_filters.size - fo = filters_options(query) - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fb/fbf038fb26f312343fb26d2a73a682ce24193c01.svn-base --- a/.svn/pristine/fb/fbf038fb26f312343fb26d2a73a682ce24193c01.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -
    <%= l(:label_preview) %> -<%= textilizable @text, :attachments => @attachements, :object => @previewed %> -
    diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fc/fc01cb7f3c8f3eb9966701ee1b49005a54a9c291.svn-base --- a/.svn/pristine/fc/fc01cb7f3c8f3eb9966701ee1b49005a54a9c291.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1081 +0,0 @@ -# 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 hour" - other: "%{count} hours" - 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}. - - 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: Модул - field_repository_is_default: Главно хранилище - field_multiple: Избор на повече от една стойност - field_auth_source_ldap_filter: LDAP филтър - field_core_fields: Стандартни полета - field_timeout: Таймаут (в секунди) - field_board_parent: Родителски форум - field_private_notes: Лични бележки - - 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: 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: Начална дата на новите задачи по подразбиране да бъде днешната дата - setting_commit_cross_project_ref: Отбелязване и приключване на задачи от други проекти, несвързани с конкретното хранилище - setting_unsubscribe: Потребителите могат да премахват профилите си - setting_session_lifetime: Максимален живот на сесиите - setting_session_timeout: Таймаут за неактивност преди прекратяване на сесиите - setting_thumbnails_enabled: Показване на миниатюри на прикачените изображения - setting_thumbnails_size: Размер на миниатюрите (в пиксели) - setting_non_working_week_days: Не работни дни - - 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_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: Управление на подзадачите - 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_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_x_issues: - zero: 0 задачи - one: 1 задача - other: "%{count} задачи" - label_total: Общо - 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_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_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_author: Author's %{name} - label_attribute_of_assigned_to: Assignee'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: С всички проекти - - 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: Този проект е затворен и е само за четене. - - 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fc/fc1e8e015812f86556df9e03ed455ae55a7dfc3a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fc/fc1e8e015812f86556df9e03ed455ae55a7dfc3a.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,174 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +namespace :redmine do + namespace :email do + + desc <<-END_DESC +Read an email from standard input. + +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=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 + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rake redmine:email:read RAILS_ENV="production" < raw_email + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rake redmine:email:read RAILS_ENV="production" \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority < raw_email +END_DESC + + task :read => :environment do + MailHandler.receive(STDIN.read, MailHandler.extract_options_from_env(ENV)) + end + + desc <<-END_DESC +Read emails from an IMAP server. + +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=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) + port=PORT IMAP server port (default: 143) + ssl=SSL Use SSL? (default: false) + username=USERNAME IMAP account + password=PASSWORD IMAP password + folder=FOLDER IMAP folder to read (default: INBOX) + +Issue attributes control options: + project=PROJECT identifier of the target project + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Processed emails control options: + move_on_success=MAILBOX move emails that were successfully received + to MAILBOX instead of deleting them + move_on_failure=MAILBOX move emails that were ignored to MAILBOX + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + + rake redmine:email:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx + + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + + rake redmine:email:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority +END_DESC + + task :receive_imap => :environment do + imap_options = {:host => ENV['host'], + :port => ENV['port'], + :ssl => ENV['ssl'], + :username => ENV['username'], + :password => ENV['password'], + :folder => ENV['folder'], + :move_on_success => ENV['move_on_success'], + :move_on_failure => ENV['move_on_failure']} + + Redmine::IMAP.check(imap_options, MailHandler.extract_options_from_env(ENV)) + end + + desc <<-END_DESC +Read emails from an POP3 server. + +Available POP3 options: + host=HOST POP3 server host (default: 127.0.0.1) + port=PORT POP3 server port (default: 110) + username=USERNAME POP3 account + password=PASSWORD POP3 password + apop=1 use APOP authentication (default: false) + delete_unprocessed=1 delete messages that could not be processed + successfully from the server (default + behaviour is to leave them on the server) + +See redmine:email:receive_imap for more options and examples. +END_DESC + + task :receive_pop3 => :environment do + pop_options = {:host => ENV['host'], + :port => ENV['port'], + :apop => ENV['apop'], + :username => ENV['username'], + :password => ENV['password'], + :delete_unprocessed => ENV['delete_unprocessed']} + + Redmine::POP3.check(pop_options, MailHandler.extract_options_from_env(ENV)) + end + + desc "Send a test email to the user with the provided login name" + task :test, [:login] => :environment do |task, args| + include Redmine::I18n + abort l(:notice_email_error, "Please include the user login to test with. Example: rake redmine:email:test[login]") if args[:login].blank? + + user = User.find_by_login(args[:login]) + abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged? + + ActionMailer::Base.raise_delivery_errors = true + begin + Mailer.with_synched_deliveries do + Mailer.test_email(user).deliver + end + puts l(:notice_email_sent, user.mail) + rescue Exception => e + abort l(:notice_email_error, e.message) + end + end + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fc/fc277d46aec92e52e80545393ffb429147c03880.svn-base --- a/.svn/pristine/fc/fc277d46aec92e52e80545393ffb429147c03880.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/bazaar_adapter' - -class Repository::Bazaar < Repository - attr_protected :root_url - validates_presence_of :url, :log_encoding - - 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::BazaarAdapter - end - - def self.scm_name - 'Bazaar' - end - - def entry(path=nil, identifier=nil) - scm.bzr_path_encodig = log_encoding - scm.entry(path, identifier) - end - - def cat(path, identifier=nil) - scm.bzr_path_encodig = log_encoding - scm.cat(path, identifier) - end - - def annotate(path, identifier=nil) - scm.bzr_path_encodig = log_encoding - scm.annotate(path, identifier) - end - - def diff(path, rev, rev_to) - scm.bzr_path_encodig = log_encoding - scm.diff(path, rev, rev_to) - end - - def entries(path=nil, identifier=nil) - scm.bzr_path_encodig = log_encoding - entries = scm.entries(path, identifier) - if entries - entries.each do |e| - next if e.lastrev.revision.blank? - # Set the filesize unless browsing a specific revision - if identifier.nil? && e.is_file? - full_path = File.join(root_url, e.path) - e.size = File.stat(full_path).size if File.file?(full_path) - end - c = Change.find( - :first, - :include => :changeset, - :conditions => [ - "#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", - e.lastrev.revision, - id - ], - :order => "#{Changeset.table_name}.revision DESC") - if c - e.lastrev.identifier = c.changeset.revision - e.lastrev.name = c.changeset.revision - e.lastrev.author = c.changeset.committer - end - end - end - load_entries_changesets(entries) - entries - end - - def fetch_changesets - scm.bzr_path_encodig = log_encoding - 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) - transaction do - revisions.reverse_each do |revision| - changeset = Changeset.create(:repository => self, - :revision => revision.identifier, - :committer => revision.author, - :committed_on => revision.time, - :scmid => revision.scmid, - :comments => revision.message) - - revision.paths.each do |change| - Change.create(:changeset => changeset, - :action => change[:action], - :path => change[:path], - :revision => change[:revision]) - end - end - end unless revisions.nil? - identifier_from = identifier_to + 1 - end - end - end - end -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fc/fc704247258255beec93055af5d732e51089fa26.svn-base --- a/.svn/pristine/fc/fc704247258255beec93055af5d732e51089fa26.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1080 +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_hours: - one: "1 hour" - other: "%{count} hours" - 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: - 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: "ș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_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}" - 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}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." - label_x_issues: - zero: 0 tichet - one: 1 tichet - other: "%{count} tichete" - 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: toate - 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fc/fcea6055312718c79b19d49925ca680df3e4895d.svn-base --- a/.svn/pristine/fc/fcea6055312718c79b19d49925ca680df3e4895d.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fd/fd1d523c594bd4b054ab83cfa0a889d70d5c4b29.svn-base --- a/.svn/pristine/fd/fd1d523c594bd4b054ab83cfa0a889d70d5c4b29.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1186 +0,0 @@ -# -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fd/fd3c31711fb05e7466806c7da438af9f8c1e0a15.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fd3c31711fb05e7466806c7da438af9f8c1e0a15.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,205 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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.where( + :committed_on => tmp_time - time_delta .. tmp_time + time_delta, + :committer => author_utf8, + :comments => cmt + ).first + # 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. + order('committed_on ASC, id ASC'). + where("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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fd/fd3ede0a5e12e3d96f4d011a2d4e4835e31d6383.svn-base --- a/.svn/pristine/fd/fd3ede0a5e12e3d96f4d011a2d4e4835e31d6383.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -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, - :workflows, - :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_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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fd/fd62d84703695db56636faed9a7f858cfe7bf11b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fd62d84703695db56636faed9a7f858cfe7bf11b.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 + + test "GET /trackers.xml 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 diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fd/fda750af1905cd4d70c559db0d1becde41e7e9ca.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fd/fda750af1905cd4d70c559db0d1becde41e7e9ca.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/fe/fe0e2a7971f5615a462d500c0c1966b4ca458e24.svn-base --- a/.svn/pristine/fe/fe0e2a7971f5615a462d500c0c1966b4ca458e24.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -<%= 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) %> -
    - - - -

    <%=h @board.name %>

    -

    <%=h @board.description %>

    - -<% if @topics.any? %> - - - - - <%= sort_header_tag('created_on', :caption => l(:field_created_on)) %> - <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %> - <%= sort_header_tag('updated_on', :caption => l(:label_message_last)) %> - - - <% @topics.each do |topic| %> - - - - - - - - <% end %> - -
    <%= l(:field_subject) %><%= l(:field_author) %>
    <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %><%= link_to_user(topic.author) %><%= format_time(topic.created_on) %><%= topic.replies_count %> - <% if topic.last_reply %> - <%= authoring topic.last_reply.created_on, topic.last_reply.author %>
    - <%= link_to_message topic.last_reply %> - <% end %> -
    -

    <%= pagination_links_full @topic_pages, @topic_count %>

    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> -<% end %> - -<% html_title @board.name %> - -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> -<% end %> diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/fe/fe4a48627f341dfca44eecc242e7bc59f020ac70.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe4a48627f341dfca44eecc242e7bc59f020ac70.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,69 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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/setup' + + 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ff/ff4ade207165498e6822191c7d47b12871a75858.svn-base --- a/.svn/pristine/ff/ff4ade207165498e6822191c7d47b12871a75858.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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.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_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 -end diff -r 261b3d9a4903 -r e248c7af89ec .svn/pristine/ff/ffce98a27e5a6dc82e77f6a0e106be536bad8666.svn-base --- a/.svn/pristine/ff/ffce98a27e5a6dc82e77f6a0e106be536bad8666.svn-base Tue Jan 14 14:37:42 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,568 +0,0 @@ -# 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 261b3d9a4903 -r e248c7af89ec .svn/pristine/ff/ffdc6711dc145662e4ade30a72d7c03a4e4d8769.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ffdc6711dc145662e4ade30a72d7c03a4e4d8769.svn-base Mon Mar 17 08:54:02 2014 +0000 @@ -0,0 +1,141 @@ +# Redmine - project management software +# Copyright (C) 2006-2014 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public 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 + before_filter :find_group, :except => [:index, :new, :create] + accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user + + helper :custom_fields + + def index + @groups = Group.sorted.all + + respond_to do |format| + format.html + format.api + end + end + + def show + respond_to do |format| + format.html + format.api + end + end + + def new + @group = Group.new + end + + def create + @group = Group.new + @group.safe_attributes = 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.api { render :action => 'show', :status => :created, :location => group_url(@group) } + else + format.html { render :action => "new" } + format.api { render_validation_errors(@group) } + end + end + end + + def edit + end + + def update + @group.safe_attributes = params[:group] + + respond_to do |format| + if @group.save + flash[:notice] = l(:notice_successful_update) + format.html { redirect_to(groups_path) } + format.api { render_api_ok } + else + format.html { render :action => "edit" } + format.api { render_validation_errors(@group) } + end + end + end + + def destroy + @group.destroy + + respond_to do |format| + format.html { redirect_to(groups_path) } + format.api { render_api_ok } + end + end + + def add_users + @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 edit_group_path(@group, :tab => 'users') } + format.js + format.api { render_api_ok } + end + end + + def remove_user + @group.users.delete(User.find(params[:user_id])) if request.delete? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'users') } + format.js + format.api { render_api_ok } + end + end + + def autocomplete_for_user + 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 edit_group_path(@group, :tab => 'memberships') } + format.js + end + end + + def destroy_membership + Member.find(params[:membership_id]).destroy if request.post? + respond_to do |format| + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } + format.js + end + end + + private + + def find_group + @group = Group.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 261b3d9a4903 -r e248c7af89ec .svn/wc.db Binary file .svn/wc.db has changed diff -r 261b3d9a4903 -r e248c7af89ec Gemfile --- a/Gemfile Tue Jan 14 14:37:42 2014 +0000 +++ b/Gemfile Mon Mar 17 08:54:02 2014 +0000 @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem "rails", "3.2.16" +gem "rails", "3.2.17" gem "jquery-rails", "~> 2.0.2" gem "coderay", "~> 1.1.0" gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] diff -r 261b3d9a4903 -r e248c7af89ec app/controllers/account_controller.rb --- a/app/controllers/account_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/account_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/activities_controller.rb --- a/app/controllers/activities_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/activities_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/admin_controller.rb --- a/app/controllers/admin_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/admin_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/application_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/attachments_controller.rb --- a/app/controllers/attachments_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/attachments_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/auth_sources_controller.rb --- a/app/controllers/auth_sources_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/auth_sources_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/auto_completes_controller.rb --- a/app/controllers/auto_completes_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/auto_completes_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/boards_controller.rb --- a/app/controllers/boards_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/boards_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/calendars_controller.rb --- a/app/controllers/calendars_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/calendars_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/comments_controller.rb --- a/app/controllers/comments_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/comments_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/context_menus_controller.rb --- a/app/controllers/context_menus_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/context_menus_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/custom_fields_controller.rb --- a/app/controllers/custom_fields_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/custom_fields_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/documents_controller.rb --- a/app/controllers/documents_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/documents_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/enumerations_controller.rb --- a/app/controllers/enumerations_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/enumerations_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/files_controller.rb --- a/app/controllers/files_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/files_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/gantts_controller.rb --- a/app/controllers/gantts_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/gantts_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/groups_controller.rb --- a/app/controllers/groups_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/groups_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/issue_categories_controller.rb --- a/app/controllers/issue_categories_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/issue_categories_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/issue_relations_controller.rb --- a/app/controllers/issue_relations_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/issue_relations_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/issue_statuses_controller.rb --- a/app/controllers/issue_statuses_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/issue_statuses_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/issues_controller.rb --- a/app/controllers/issues_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/issues_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/journals_controller.rb --- a/app/controllers/journals_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/journals_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/mail_handler_controller.rb --- a/app/controllers/mail_handler_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/mail_handler_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/members_controller.rb --- a/app/controllers/members_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/members_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/messages_controller.rb --- a/app/controllers/messages_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/messages_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/my_controller.rb --- a/app/controllers/my_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/my_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/news_controller.rb --- a/app/controllers/news_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/news_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/previews_controller.rb --- a/app/controllers/previews_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/previews_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/project_enumerations_controller.rb --- a/app/controllers/project_enumerations_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/project_enumerations_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/projects_controller.rb --- a/app/controllers/projects_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/projects_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/queries_controller.rb --- a/app/controllers/queries_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/queries_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/reports_controller.rb --- a/app/controllers/reports_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/reports_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/repositories_controller.rb --- a/app/controllers/repositories_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/repositories_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/roles_controller.rb --- a/app/controllers/roles_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/roles_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/search_controller.rb --- a/app/controllers/search_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/search_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/settings_controller.rb --- a/app/controllers/settings_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/settings_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/sys_controller.rb --- a/app/controllers/sys_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/sys_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/timelog_controller.rb --- a/app/controllers/timelog_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/timelog_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/trackers_controller.rb --- a/app/controllers/trackers_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/trackers_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/users_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/versions_controller.rb --- a/app/controllers/versions_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/versions_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/watchers_controller.rb --- a/app/controllers/watchers_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/watchers_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/welcome_controller.rb --- a/app/controllers/welcome_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/welcome_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/wiki_controller.rb --- a/app/controllers/wiki_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/wiki_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/wikis_controller.rb --- a/app/controllers/wikis_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/wikis_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/controllers/workflows_controller.rb --- a/app/controllers/workflows_controller.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/controllers/workflows_controller.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/account_helper.rb --- a/app/helpers/account_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/account_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/activities_helper.rb --- a/app/helpers/activities_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/activities_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/admin_helper.rb --- a/app/helpers/admin_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/admin_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/application_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/attachments_helper.rb --- a/app/helpers/attachments_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/attachments_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/auth_sources_helper.rb --- a/app/helpers/auth_sources_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/auth_sources_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/boards_helper.rb --- a/app/helpers/boards_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/boards_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/calendars_helper.rb --- a/app/helpers/calendars_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/calendars_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/context_menus_helper.rb --- a/app/helpers/context_menus_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/context_menus_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/custom_fields_helper.rb --- a/app/helpers/custom_fields_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/custom_fields_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/documents_helper.rb --- a/app/helpers/documents_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/documents_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/enumerations_helper.rb --- a/app/helpers/enumerations_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/enumerations_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/gantt_helper.rb --- a/app/helpers/gantt_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/gantt_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/groups_helper.rb --- a/app/helpers/groups_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/groups_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/issue_categories_helper.rb --- a/app/helpers/issue_categories_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/issue_categories_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/issue_relations_helper.rb --- a/app/helpers/issue_relations_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/issue_relations_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/issue_statuses_helper.rb --- a/app/helpers/issue_statuses_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/issue_statuses_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/issues_helper.rb --- a/app/helpers/issues_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/issues_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/journals_helper.rb --- a/app/helpers/journals_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/journals_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/mail_handler_helper.rb --- a/app/helpers/mail_handler_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/mail_handler_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/members_helper.rb --- a/app/helpers/members_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/members_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/messages_helper.rb --- a/app/helpers/messages_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/messages_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/my_helper.rb --- a/app/helpers/my_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/my_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/news_helper.rb --- a/app/helpers/news_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/news_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/projects_helper.rb --- a/app/helpers/projects_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/projects_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/queries_helper.rb --- a/app/helpers/queries_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/queries_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -155,6 +155,10 @@ when 'IssueRelation' other = value.other_issue(issue) l(value.label_for(issue)) + " ##{other.id}" + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) else value.to_s end diff -r 261b3d9a4903 -r e248c7af89ec app/helpers/reports_helper.rb --- a/app/helpers/reports_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/reports_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/repositories_helper.rb --- a/app/helpers/repositories_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/repositories_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/roles_helper.rb --- a/app/helpers/roles_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/roles_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/routes_helper.rb --- a/app/helpers/routes_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/routes_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/search_helper.rb --- a/app/helpers/search_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/search_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/settings_helper.rb --- a/app/helpers/settings_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/settings_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/timelog_helper.rb --- a/app/helpers/timelog_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/timelog_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/trackers_helper.rb --- a/app/helpers/trackers_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/trackers_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/users_helper.rb --- a/app/helpers/users_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/users_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/versions_helper.rb --- a/app/helpers/versions_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/versions_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/watchers_helper.rb --- a/app/helpers/watchers_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/watchers_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/welcome_helper.rb --- a/app/helpers/welcome_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/welcome_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/wiki_helper.rb --- a/app/helpers/wiki_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/wiki_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/helpers/workflows_helper.rb --- a/app/helpers/workflows_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/helpers/workflows_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/attachment.rb --- a/app/models/attachment.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/attachment.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/auth_source.rb --- a/app/models/auth_source.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/auth_source.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/auth_source_ldap.rb --- a/app/models/auth_source_ldap.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/auth_source_ldap.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/board.rb --- a/app/models/board.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/board.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/change.rb --- a/app/models/change.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/change.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/changeset.rb --- a/app/models/changeset.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/changeset.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -154,13 +154,14 @@ end def text_tag(ref_project=nil) + repo = "" + if repository && repository.identifier.present? + repo = "#{repository.identifier}|" + end tag = if scmid? - "commit:#{scmid}" + "commit:#{repo}#{scmid}" else - "r#{revision}" - end - if repository && repository.identifier.present? - tag = "#{repository.identifier}|#{tag}" + "#{repo}r#{revision}" end if ref_project && project && ref_project != project tag = "#{project.identifier}:#{tag}" diff -r 261b3d9a4903 -r e248c7af89ec app/models/comment.rb --- a/app/models/comment.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/comment.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/custom_field.rb --- a/app/models/custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -350,7 +350,7 @@ # Returns the error message for the given value regarding its format def validate_field_value_format(value) errs = [] - if value.present? + unless value.to_s == '' 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 diff -r 261b3d9a4903 -r e248c7af89ec app/models/custom_field_value.rb --- a/app/models/custom_field_value.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/custom_field_value.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/custom_value.rb --- a/app/models/custom_value.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/custom_value.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/document.rb --- a/app/models/document.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/document.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/document_category.rb --- a/app/models/document_category.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/document_category.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/document_category_custom_field.rb --- a/app/models/document_category_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/document_category_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/enabled_module.rb --- a/app/models/enabled_module.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/enabled_module.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/enumeration.rb --- a/app/models/enumeration.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/enumeration.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/group.rb --- a/app/models/group.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/group.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/group_custom_field.rb --- a/app/models/group_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/group_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue.rb --- a/app/models/issue.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -1089,7 +1089,7 @@ if user.logged? s << ' created-by-me' if author_id == user.id s << ' assigned-to-me' if assigned_to_id == user.id - s << ' assigned-to-my-group' if user.groups.any? {|g| g.id = assigned_to_id} + s << ' assigned-to-my-group' if user.groups.any? {|g| g.id == assigned_to_id} end s end @@ -1352,7 +1352,7 @@ 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 + average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f if average == 0 average = 1 end diff -r 261b3d9a4903 -r e248c7af89ec app/models/issue_category.rb --- a/app/models/issue_category.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_category.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_custom_field.rb --- a/app/models/issue_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_priority.rb --- a/app/models/issue_priority.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_priority.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_priority_custom_field.rb --- a/app/models/issue_priority_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_priority_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_query.rb --- a/app/models/issue_query.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_query.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_relation.rb --- a/app/models/issue_relation.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_relation.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/issue_status.rb --- a/app/models/issue_status.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/issue_status.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/journal.rb --- a/app/models/journal.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/journal.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/journal_detail.rb --- a/app/models/journal_detail.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/journal_detail.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/mail_handler.rb --- a/app/models/mail_handler.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/mail_handler.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/mailer.rb --- a/app/models/mailer.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/mailer.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/member.rb --- a/app/models/member.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/member.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/member_role.rb --- a/app/models/member_role.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/member_role.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/message.rb --- a/app/models/message.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/message.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/news.rb --- a/app/models/news.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/news.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/principal.rb --- a/app/models/principal.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/principal.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/project.rb --- a/app/models/project.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/project.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/project_custom_field.rb --- a/app/models/project_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/project_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/query.rb --- a/app/models/query.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/query.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/repository.rb --- a/app/models/repository.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -153,6 +153,12 @@ end end + # TODO: should return an empty hash instead of nil to avoid many ||{} + def extra_info + h = read_attribute(:extra_info) + h.is_a?(Hash) ? h : nil + end + def merge_extra_info(arg) h = extra_info || {} return h if arg.nil? diff -r 261b3d9a4903 -r e248c7af89ec app/models/repository/bazaar.rb --- a/app/models/repository/bazaar.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/bazaar.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/repository/cvs.rb --- a/app/models/repository/cvs.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/cvs.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/repository/darcs.rb --- a/app/models/repository/darcs.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/darcs.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/repository/filesystem.rb --- a/app/models/repository/filesystem.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/filesystem.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r 261b3d9a4903 -r e248c7af89ec app/models/repository/git.rb --- a/app/models/repository/git.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/git.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com # # This program is free software; you can redistribute it and/or diff -r 261b3d9a4903 -r e248c7af89ec app/models/repository/mercurial.rb --- a/app/models/repository/mercurial.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/mercurial.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/repository/subversion.rb --- a/app/models/repository/subversion.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/repository/subversion.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/role.rb --- a/app/models/role.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/role.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/setting.rb --- a/app/models/setting.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/setting.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/time_entry.rb --- a/app/models/time_entry.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/time_entry.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/time_entry_activity.rb --- a/app/models/time_entry_activity.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/time_entry_activity.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/time_entry_activity_custom_field.rb --- a/app/models/time_entry_activity_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/time_entry_activity_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/time_entry_custom_field.rb --- a/app/models/time_entry_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/time_entry_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/time_entry_query.rb --- a/app/models/time_entry_query.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/time_entry_query.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/token.rb --- a/app/models/token.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/token.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/tracker.rb --- a/app/models/tracker.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/tracker.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/user.rb --- a/app/models/user.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/user.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/user_custom_field.rb --- a/app/models/user_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/user_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/user_preference.rb --- a/app/models/user_preference.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/user_preference.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/version.rb --- a/app/models/version.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/version.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/version_custom_field.rb --- a/app/models/version_custom_field.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/version_custom_field.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/watcher.rb --- a/app/models/watcher.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/watcher.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/wiki.rb --- a/app/models/wiki.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/wiki.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/wiki_content.rb --- a/app/models/wiki_content.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/wiki_content.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/wiki_page.rb --- a/app/models/wiki_page.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/wiki_page.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/wiki_redirect.rb --- a/app/models/wiki_redirect.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/wiki_redirect.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/workflow_permission.rb --- a/app/models/workflow_permission.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/workflow_permission.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/workflow_rule.rb --- a/app/models/workflow_rule.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/workflow_rule.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/models/workflow_transition.rb --- a/app/models/workflow_transition.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/models/workflow_transition.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec app/views/issues/_form.html.erb --- a/app/views/issues/_form.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/issues/_form.html.erb Mon Mar 17 08:54:02 2014 +0000 @@ -43,3 +43,5 @@ <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> <% end %> + +<% heads_for_wiki_formatter %> diff -r 261b3d9a4903 -r e248c7af89ec app/views/layouts/base.html.erb --- a/app/views/layouts/base.html.erb Tue Jan 14 14:37:42 2014 +0000 +++ b/app/views/layouts/base.html.erb Mon Mar 17 08:54:02 2014 +0000 @@ -70,7 +70,7 @@ diff -r 261b3d9a4903 -r e248c7af89ec config/locales/pt-BR.yml --- a/config/locales/pt-BR.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/locales/pt-BR.yml Mon Mar 17 08:54:02 2014 +0000 @@ -1109,15 +1109,14 @@ field_generate_password: Gerar senha setting_default_projects_tracker_ids: Tipos padrões para novos projeto label_total_time: Total - notice_account_not_activated_yet: You haven't activated your account yet. If you want - to receive a new activation email, please click this link. - notice_account_locked: Your account is locked. - label_hidden: Hidden - label_visibility_private: to me only - label_visibility_roles: to these roles only - label_visibility_public: to any users - field_must_change_passwd: Must change password at next logon - notice_new_password_must_be_different: The new password must be different from the - current password - setting_mail_handler_excluded_filenames: Exclude attachments by name - text_convert_available: ImageMagick convert available (optional) + notice_account_not_activated_yet: Sua conta ainda não foi ativada. Se você deseja receber + um novo email de ativação, por favor clique aqui. + notice_account_locked: Sua conta está bloqueada. + label_hidden: Visibilidade + label_visibility_private: para mim + label_visibility_roles: para os papéis + label_visibility_public: para qualquer usuário + field_must_change_passwd: É necessário alterar sua senha na próxima vez que tentar acessar sua conta + notice_new_password_must_be_different: A nova senha deve ser diferente da senha atual + setting_mail_handler_excluded_filenames: Exclui anexos por nome + text_convert_available: Conversor ImageMagick disponível (opcional) diff -r 261b3d9a4903 -r e248c7af89ec config/routes.rb --- a/config/routes.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/config/routes.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec config/settings.yml --- a/config/settings.yml Tue Jan 14 14:37:42 2014 +0000 +++ b/config/settings.yml Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec doc/CHANGELOG --- a/doc/CHANGELOG Tue Jan 14 14:37:42 2014 +0000 +++ b/doc/CHANGELOG Mon Mar 17 08:54:02 2014 +0000 @@ -1,9 +1,28 @@ == Redmine changelog Redmine - project management software -Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ +== 2014-03-02 v2.4.4 + +* Defect #16081: Export CSV - Custom field true/false not using translation +* Defect #16161: Parent task search and datepicker not available after changing status +* Defect #16169: Wrong validation when updating integer custom field with spaces +* Defect #16177: Mercurial 2.9 compatibility + +== 2014-02-08 v2.4.3 + +* Defect #13544: Commit reference: autogenerated issue note has wrong commit link syntax in multi-repo or cross-project context +* Defect #15664: Unable to upload attachments without add_issues, edit_issues or add_issue_notes permission +* Defect #15756: 500 on admin info/settings page on development environment +* Defect #15781: Customfields have a noticable impact on search performance due to slow database COUNT +* Defect #15849: Redmine:Fetch_Changesets Single-inheritance issue in subclass "Repository:Git" +* Defect #15870: Parent task completion is 104% after update of subtasks +* Defect #16032: Repository.fetch_changesets > app/models/repository/git.rb:137:in `[]=': string not matched (IndexError) +* Defect #16038: Issue#css_classes corrupts user.groups association cache +* Patch #15960: pt-BR translation for 2.4-stable + == 2013-12-23 v2.4.2 * Defect #15398: HTML 5 invalid
    tag diff -r 261b3d9a4903 -r e248c7af89ec doc/INSTALL --- a/doc/INSTALL Tue Jan 14 14:37:42 2014 +0000 +++ b/doc/INSTALL Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ == Redmine installation Redmine - project management software -Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ diff -r 261b3d9a4903 -r e248c7af89ec doc/README_FOR_APP --- a/doc/README_FOR_APP Tue Jan 14 14:37:42 2014 +0000 +++ b/doc/README_FOR_APP Mon Mar 17 08:54:02 2014 +0000 @@ -6,7 +6,7 @@ = License -Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec doc/UPGRADING --- a/doc/UPGRADING Tue Jan 14 14:37:42 2014 +0000 +++ b/doc/UPGRADING Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ == Redmine upgrade Redmine - project management software -Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2006-2014 Jean-Philippe Lang http://www.redmine.org/ diff -r 261b3d9a4903 -r e248c7af89ec extra/mail_handler/rdm-mailhandler.rb --- a/extra/mail_handler/rdm-mailhandler.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/extra/mail_handler/rdm-mailhandler.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/env ruby # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec 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 Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb --- a/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb --- a/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/plugins/acts_as_event/lib/acts_as_event.rb --- a/lib/plugins/acts_as_event/lib/acts_as_event.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/plugins/acts_as_event/lib/acts_as_event.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb --- a/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine.rb --- a/lib/redmine.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -140,20 +140,20 @@ end map.project_module :news do |map| - map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member + map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :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 :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin + map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :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 :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin map.permission :view_files, {:files => :index, :versions => :download}, :read => true end @@ -164,7 +164,7 @@ 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 :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload map.permission :delete_wiki_pages_attachments, {} map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member end @@ -180,9 +180,9 @@ 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 :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload} + map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member + map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin map.permission :delete_messages, {:messages => :destroy}, :require => :member map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin end diff -r 261b3d9a4903 -r e248c7af89ec lib/redmine/access_control.rb --- a/lib/redmine/access_control.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/access_control.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/access_keys.rb --- a/lib/redmine/access_keys.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/access_keys.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/activity.rb --- a/lib/redmine/activity.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/activity.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/activity/fetcher.rb --- a/lib/redmine/activity/fetcher.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/activity/fetcher.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/ciphering.rb --- a/lib/redmine/ciphering.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/ciphering.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/configuration.rb --- a/lib/redmine/configuration.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/configuration.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/core_ext/active_record.rb --- a/lib/redmine/core_ext/active_record.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/core_ext/active_record.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/core_ext/date/calculations.rb --- a/lib/redmine/core_ext/date/calculations.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/core_ext/date/calculations.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/core_ext/string/conversions.rb --- a/lib/redmine/core_ext/string/conversions.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/core_ext/string/conversions.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/core_ext/string/inflections.rb --- a/lib/redmine/core_ext/string/inflections.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/core_ext/string/inflections.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/default_data/loader.rb --- a/lib/redmine/default_data/loader.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/default_data/loader.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/export/pdf.rb --- a/lib/redmine/export/pdf.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/export/pdf.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/helpers/calendar.rb --- a/lib/redmine/helpers/calendar.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/helpers/calendar.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/helpers/diff.rb --- a/lib/redmine/helpers/diff.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/helpers/diff.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/helpers/gantt.rb --- a/lib/redmine/helpers/gantt.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/helpers/gantt.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/helpers/time_report.rb --- a/lib/redmine/helpers/time_report.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/helpers/time_report.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/hook.rb --- a/lib/redmine/hook.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/hook.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/i18n.rb --- a/lib/redmine/i18n.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/i18n.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/imap.rb --- a/lib/redmine/imap.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/imap.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/menu_manager.rb --- a/lib/redmine/menu_manager.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/menu_manager.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/mime_type.rb --- a/lib/redmine/mime_type.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/mime_type.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/pagination.rb --- a/lib/redmine/pagination.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/pagination.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/platform.rb --- a/lib/redmine/platform.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/platform.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/plugin.rb --- a/lib/redmine/plugin.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/plugin.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/pop3.rb --- a/lib/redmine/pop3.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/pop3.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/safe_attributes.rb --- a/lib/redmine/safe_attributes.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/safe_attributes.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters.rb --- a/lib/redmine/scm/adapters.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/abstract_adapter.rb --- a/lib/redmine/scm/adapters/abstract_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/abstract_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/bazaar_adapter.rb --- a/lib/redmine/scm/adapters/bazaar_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/darcs_adapter.rb --- a/lib/redmine/scm/adapters/darcs_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/darcs_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/filesystem_adapter.rb --- a/lib/redmine/scm/adapters/filesystem_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/git_adapter.rb --- a/lib/redmine/scm/adapters/git_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/git_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/mercurial/redminehelper.py --- a/lib/redmine/scm/adapters/mercurial/redminehelper.py Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/mercurial/redminehelper.py Mon Mar 17 08:54:02 2014 +0000 @@ -79,8 +79,13 @@ def _branches(ui, repo): # see mercurial/commands.py:branches def iterbranches(): - for t, n in repo.branchtags().iteritems(): - yield t, n, repo.changelog.rev(n) + if getattr(repo, 'branchtags', None) is not None: + # Mercurial < 2.9 + for t, n in repo.branchtags().iteritems(): + yield t, n, repo.changelog.rev(n) + else: + for tag, heads, tip, isclosed in repo.branchmap().iterbranches(): + yield tag, tip, repo.changelog.rev(tip) def branchheads(branch): try: return repo.branchheads(branch, closed=False) diff -r 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/mercurial_adapter.rb --- a/lib/redmine/scm/adapters/mercurial_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/scm/adapters/subversion_adapter.rb --- a/lib/redmine/scm/adapters/subversion_adapter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/scm/adapters/subversion_adapter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/search.rb --- a/lib/redmine/search.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/search.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/subclass_factory.rb --- a/lib/redmine/subclass_factory.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/subclass_factory.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/syntax_highlighting.rb --- a/lib/redmine/syntax_highlighting.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/syntax_highlighting.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/themes.rb --- a/lib/redmine/themes.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/themes.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/thumbnail.rb --- a/lib/redmine/thumbnail.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/thumbnail.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/unified_diff.rb --- a/lib/redmine/unified_diff.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/unified_diff.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/utils.rb --- a/lib/redmine/utils.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/utils.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/version.rb --- a/lib/redmine/version.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/version.rb Mon Mar 17 08:54:02 2014 +0000 @@ -4,7 +4,7 @@ module VERSION #:nodoc: MAJOR = 2 MINOR = 4 - TINY = 2 + TINY = 4 # Branch values: # * official release: nil diff -r 261b3d9a4903 -r e248c7af89ec lib/redmine/views/api_template_handler.rb --- a/lib/redmine/views/api_template_handler.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/api_template_handler.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/builders.rb --- a/lib/redmine/views/builders.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/builders.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/builders/json.rb --- a/lib/redmine/views/builders/json.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/builders/json.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/builders/structure.rb --- a/lib/redmine/views/builders/structure.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/builders/structure.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/builders/xml.rb --- a/lib/redmine/views/builders/xml.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/builders/xml.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/labelled_form_builder.rb --- a/lib/redmine/views/labelled_form_builder.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/labelled_form_builder.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/my_page/block.rb --- a/lib/redmine/views/my_page/block.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/my_page/block.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/views/other_formats_builder.rb --- a/lib/redmine/views/other_formats_builder.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/views/other_formats_builder.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/wiki_formatting.rb --- a/lib/redmine/wiki_formatting.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/wiki_formatting.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/wiki_formatting/macros.rb --- a/lib/redmine/wiki_formatting/macros.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/wiki_formatting/macros.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/wiki_formatting/textile/formatter.rb --- a/lib/redmine/wiki_formatting/textile/formatter.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/wiki_formatting/textile/formatter.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/redmine/wiki_formatting/textile/helper.rb --- a/lib/redmine/wiki_formatting/textile/helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/redmine/wiki_formatting/textile/helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/ciphering.rake --- a/lib/tasks/ciphering.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/ciphering.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/email.rake --- a/lib/tasks/email.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/email.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/migrate_from_mantis.rake --- a/lib/tasks/migrate_from_mantis.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/migrate_from_mantis.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/migrate_from_trac.rake --- a/lib/tasks/migrate_from_trac.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/migrate_from_trac.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/redmine.rake --- a/lib/tasks/redmine.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/redmine.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec lib/tasks/reminder.rake --- a/lib/tasks/reminder.rake Tue Jan 14 14:37:42 2014 +0000 +++ b/lib/tasks/reminder.rake Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec public/javascripts/application.js --- a/public/javascripts/application.js Tue Jan 14 14:37:42 2014 +0000 +++ b/public/javascripts/application.js Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ /* Redmine - project management software - Copyright (C) 2006-2013 Jean-Philippe Lang */ + Copyright (C) 2006-2014 Jean-Philippe Lang */ function checkAll(id, checked) { $('#'+id).find('input[type=checkbox]:enabled').attr('checked', checked); diff -r 261b3d9a4903 -r e248c7af89ec public/javascripts/attachments.js --- a/public/javascripts/attachments.js Tue Jan 14 14:37:42 2014 +0000 +++ b/public/javascripts/attachments.js Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ /* Redmine - project management software - Copyright (C) 2006-2013 Jean-Philippe Lang */ + Copyright (C) 2006-2014 Jean-Philippe Lang */ function addFile(inputEl, file, eagerUpload) { diff -r 261b3d9a4903 -r e248c7af89ec test/extra/redmine_pm/repository_git_test.rb --- a/test/extra/redmine_pm/repository_git_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/extra/redmine_pm/repository_git_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/extra/redmine_pm/repository_subversion_test.rb --- a/test/extra/redmine_pm/repository_subversion_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/extra/redmine_pm/repository_subversion_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/extra/redmine_pm/test_case.rb --- a/test/extra/redmine_pm/test_case.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/extra/redmine_pm/test_case.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/fixtures/repositories/mercurial_repository.hg Binary file test/fixtures/repositories/mercurial_repository.hg has changed diff -r 261b3d9a4903 -r e248c7af89ec test/functional/account_controller_openid_test.rb --- a/test/functional/account_controller_openid_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/account_controller_openid_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/account_controller_test.rb --- a/test/functional/account_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/account_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/activities_controller_test.rb --- a/test/functional/activities_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/activities_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/admin_controller_test.rb --- a/test/functional/admin_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/admin_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/attachments_controller_test.rb --- a/test/functional/attachments_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/attachments_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/auth_sources_controller_test.rb --- a/test/functional/auth_sources_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/auth_sources_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/auto_completes_controller_test.rb --- a/test/functional/auto_completes_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/auto_completes_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/boards_controller_test.rb --- a/test/functional/boards_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/boards_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/calendars_controller_test.rb --- a/test/functional/calendars_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/calendars_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/comments_controller_test.rb --- a/test/functional/comments_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/comments_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/context_menus_controller_test.rb --- a/test/functional/context_menus_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/context_menus_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/custom_fields_controller_test.rb --- a/test/functional/custom_fields_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/custom_fields_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/documents_controller_test.rb --- a/test/functional/documents_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/documents_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/enumerations_controller_test.rb --- a/test/functional/enumerations_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/enumerations_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/files_controller_test.rb --- a/test/functional/files_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/files_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/gantts_controller_test.rb --- a/test/functional/gantts_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/gantts_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/groups_controller_test.rb --- a/test/functional/groups_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/groups_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issue_categories_controller_test.rb --- a/test/functional/issue_categories_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issue_categories_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issue_relations_controller_test.rb --- a/test/functional/issue_relations_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issue_relations_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issue_statuses_controller_test.rb --- a/test/functional/issue_statuses_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issue_statuses_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issues_controller_test.rb --- a/test/functional/issues_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issues_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issues_controller_transaction_test.rb --- a/test/functional/issues_controller_transaction_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issues_controller_transaction_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/issues_custom_fields_visibility_test.rb --- a/test/functional/issues_custom_fields_visibility_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/issues_custom_fields_visibility_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/journals_controller_test.rb --- a/test/functional/journals_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/journals_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/mail_handler_controller_test.rb --- a/test/functional/mail_handler_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/mail_handler_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/members_controller_test.rb --- a/test/functional/members_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/members_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/messages_controller_test.rb --- a/test/functional/messages_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/messages_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/my_controller_test.rb --- a/test/functional/my_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/my_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/news_controller_test.rb --- a/test/functional/news_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/news_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/previews_controller_test.rb --- a/test/functional/previews_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/previews_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/project_enumerations_controller_test.rb --- a/test/functional/project_enumerations_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/project_enumerations_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/projects_controller_test.rb --- a/test/functional/projects_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/projects_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/queries_controller_test.rb --- a/test/functional/queries_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/queries_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/reports_controller_test.rb --- a/test/functional/reports_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/reports_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_bazaar_controller_test.rb --- a/test/functional/repositories_bazaar_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_bazaar_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_controller_test.rb --- a/test/functional/repositories_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_cvs_controller_test.rb --- a/test/functional/repositories_cvs_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_cvs_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_darcs_controller_test.rb --- a/test/functional/repositories_darcs_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_darcs_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_filesystem_controller_test.rb --- a/test/functional/repositories_filesystem_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_filesystem_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_git_controller_test.rb --- a/test/functional/repositories_git_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_git_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/repositories_mercurial_controller_test.rb --- a/test/functional/repositories_mercurial_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_mercurial_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 @@ REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s CHAR_1_HEX = "\xc3\x9c" PRJ_ID = 3 - NUM_REV = 32 + NUM_REV = 34 ruby19_non_utf8_pass = (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') diff -r 261b3d9a4903 -r e248c7af89ec test/functional/repositories_subversion_controller_test.rb --- a/test/functional/repositories_subversion_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/repositories_subversion_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/roles_controller_test.rb --- a/test/functional/roles_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/roles_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/search_controller_test.rb --- a/test/functional/search_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/search_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/search_custom_fields_visibility_test.rb --- a/test/functional/search_custom_fields_visibility_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/search_custom_fields_visibility_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/sessions_test.rb --- a/test/functional/sessions_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/sessions_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/settings_controller_test.rb --- a/test/functional/settings_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/settings_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/sys_controller_test.rb --- a/test/functional/sys_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/sys_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/time_entry_reports_controller_test.rb --- a/test/functional/time_entry_reports_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/time_entry_reports_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/timelog_controller_test.rb --- a/test/functional/timelog_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/timelog_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/timelog_custom_fields_visibility_test.rb --- a/test/functional/timelog_custom_fields_visibility_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/timelog_custom_fields_visibility_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/trackers_controller_test.rb --- a/test/functional/trackers_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/trackers_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/users_controller_test.rb --- a/test/functional/users_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/users_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/versions_controller_test.rb --- a/test/functional/versions_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/versions_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/watchers_controller_test.rb --- a/test/functional/watchers_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/watchers_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/welcome_controller_test.rb --- a/test/functional/welcome_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/welcome_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/wiki_controller_test.rb --- a/test/functional/wiki_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/wiki_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/wikis_controller_test.rb --- a/test/functional/wikis_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/wikis_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/functional/workflows_controller_test.rb --- a/test/functional/workflows_controller_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/functional/workflows_controller_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/account_test.rb --- a/test/integration/account_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/account_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/admin_test.rb --- a/test/integration/admin_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/admin_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/api_test.rb --- a/test/integration/api_test/api_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/api_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/attachments_test.rb --- a/test/integration/api_test/attachments_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/attachments_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/authentication_test.rb --- a/test/integration/api_test/authentication_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/authentication_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/custom_fields_test.rb --- a/test/integration/api_test/custom_fields_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/custom_fields_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/disabled_rest_api_test.rb --- a/test/integration/api_test/disabled_rest_api_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/disabled_rest_api_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/enumerations_test.rb --- a/test/integration/api_test/enumerations_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/enumerations_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/groups_test.rb --- a/test/integration/api_test/groups_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/groups_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/http_basic_login_test.rb --- a/test/integration/api_test/http_basic_login_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/http_basic_login_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec 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 Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/http_basic_login_with_api_token_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/issue_categories_test.rb --- a/test/integration/api_test/issue_categories_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/issue_categories_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/issue_relations_test.rb --- a/test/integration/api_test/issue_relations_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/issue_relations_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/issue_statuses_test.rb --- a/test/integration/api_test/issue_statuses_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/issue_statuses_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/issues_test.rb --- a/test/integration/api_test/issues_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/issues_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/jsonp_test.rb --- a/test/integration/api_test/jsonp_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/jsonp_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/memberships_test.rb --- a/test/integration/api_test/memberships_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/memberships_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/news_test.rb --- a/test/integration/api_test/news_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/news_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/projects_test.rb --- a/test/integration/api_test/projects_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/projects_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/queries_test.rb --- a/test/integration/api_test/queries_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/queries_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/roles_test.rb --- a/test/integration/api_test/roles_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/roles_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/time_entries_test.rb --- a/test/integration/api_test/time_entries_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/time_entries_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/token_authentication_test.rb --- a/test/integration/api_test/token_authentication_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/token_authentication_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/trackers_test.rb --- a/test/integration/api_test/trackers_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/trackers_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/users_test.rb --- a/test/integration/api_test/users_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/users_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/versions_test.rb --- a/test/integration/api_test/versions_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/versions_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/api_test/wiki_pages_test.rb --- a/test/integration/api_test/wiki_pages_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/api_test/wiki_pages_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/application_test.rb --- a/test/integration/application_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/application_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/attachments_test.rb --- a/test/integration/attachments_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/attachments_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/issues_test.rb --- a/test/integration/issues_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/issues_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/layout_test.rb --- a/test/integration/layout_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/layout_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/lib/redmine/hook_test.rb --- a/test/integration/lib/redmine/hook_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/lib/redmine/hook_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/lib/redmine/menu_manager_test.rb --- a/test/integration/lib/redmine/menu_manager_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/lib/redmine/menu_manager_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/lib/redmine/themes_test.rb --- a/test/integration/lib/redmine/themes_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/lib/redmine/themes_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/projects_test.rb --- a/test/integration/projects_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/projects_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/repositories_git_test.rb --- a/test/integration/repositories_git_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/repositories_git_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/account_test.rb --- a/test/integration/routing/account_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/account_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/activities_test.rb --- a/test/integration/routing/activities_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/activities_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/admin_test.rb --- a/test/integration/routing/admin_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/admin_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/attachments_test.rb --- a/test/integration/routing/attachments_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/attachments_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/auth_sources_test.rb --- a/test/integration/routing/auth_sources_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/auth_sources_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/auto_completes_test.rb --- a/test/integration/routing/auto_completes_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/auto_completes_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/boards_test.rb --- a/test/integration/routing/boards_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/boards_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/calendars_test.rb --- a/test/integration/routing/calendars_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/calendars_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/comments_test.rb --- a/test/integration/routing/comments_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/comments_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/context_menus_test.rb --- a/test/integration/routing/context_menus_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/context_menus_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/custom_fields_test.rb --- a/test/integration/routing/custom_fields_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/custom_fields_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/documents_test.rb --- a/test/integration/routing/documents_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/documents_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/enumerations_test.rb --- a/test/integration/routing/enumerations_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/enumerations_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/files_test.rb --- a/test/integration/routing/files_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/files_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/gantts_test.rb --- a/test/integration/routing/gantts_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/gantts_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/groups_test.rb --- a/test/integration/routing/groups_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/groups_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/issue_categories_test.rb --- a/test/integration/routing/issue_categories_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/issue_categories_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/issue_relations_test.rb --- a/test/integration/routing/issue_relations_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/issue_relations_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/issue_statuses_test.rb --- a/test/integration/routing/issue_statuses_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/issue_statuses_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/issues_test.rb --- a/test/integration/routing/issues_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/issues_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/journals_test.rb --- a/test/integration/routing/journals_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/journals_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/mail_handler_test.rb --- a/test/integration/routing/mail_handler_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/mail_handler_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/members_test.rb --- a/test/integration/routing/members_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/members_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/messages_test.rb --- a/test/integration/routing/messages_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/messages_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/my_test.rb --- a/test/integration/routing/my_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/my_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/news_test.rb --- a/test/integration/routing/news_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/news_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/previews_test.rb --- a/test/integration/routing/previews_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/previews_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/project_enumerations_test.rb --- a/test/integration/routing/project_enumerations_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/project_enumerations_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/projects_test.rb --- a/test/integration/routing/projects_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/projects_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/queries_test.rb --- a/test/integration/routing/queries_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/queries_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/reports_test.rb --- a/test/integration/routing/reports_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/reports_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/repositories_test.rb --- a/test/integration/routing/repositories_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/repositories_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/roles_test.rb --- a/test/integration/routing/roles_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/roles_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/search_test.rb --- a/test/integration/routing/search_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/search_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/settings_test.rb --- a/test/integration/routing/settings_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/settings_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/sys_test.rb --- a/test/integration/routing/sys_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/sys_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/timelog_test.rb --- a/test/integration/routing/timelog_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/timelog_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/trackers_test.rb --- a/test/integration/routing/trackers_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/trackers_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/users_test.rb --- a/test/integration/routing/users_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/users_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/versions_test.rb --- a/test/integration/routing/versions_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/versions_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/watchers_test.rb --- a/test/integration/routing/watchers_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/watchers_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/welcome_test.rb --- a/test/integration/routing/welcome_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/welcome_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/wiki_test.rb --- a/test/integration/routing/wiki_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/wiki_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/wikis_test.rb --- a/test/integration/routing/wikis_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/wikis_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/routing/workflows_test.rb --- a/test/integration/routing/workflows_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/routing/workflows_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/integration/users_test.rb --- a/test/integration/users_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/integration/users_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/test_helper.rb --- a/test/test_helper.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/test_helper.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -111,6 +111,14 @@ User.current = saved_user end + def with_locale(locale, &block) + saved_localed = ::I18n.locale + ::I18n.locale = locale + yield + ensure + ::I18n.locale = saved_localed + end + def change_user_password(login, new_password) user = User.where(:login => login).first user.password, user.password_confirmation = new_password, new_password diff -r 261b3d9a4903 -r e248c7af89ec test/ui/base.rb --- a/test/ui/base.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/ui/base.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/ui/issues_test.rb --- a/test/ui/issues_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/ui/issues_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/activity_test.rb --- a/test/unit/activity_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/activity_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/attachment_test.rb --- a/test/unit/attachment_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/attachment_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/auth_source_ldap_test.rb --- a/test/unit/auth_source_ldap_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/auth_source_ldap_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/board_test.rb --- a/test/unit/board_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/board_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/changeset_test.rb --- a/test/unit/changeset_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/changeset_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -345,6 +345,16 @@ assert_equal 'commit:0123456789', c.text_tag end + def test_text_tag_hash_with_repository_identifier + r = Repository::Subversion.new( + :project_id => 1, + :url => 'svn://localhost/test', + :identifier => 'documents') + c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => r) + assert_equal 'commit:documents|7234cb27', c.text_tag + assert_equal 'ecookbook:commit:documents|7234cb27', c.text_tag(Project.find(2)) + end + def test_previous changeset = Changeset.find_by_revision('3') assert_equal Changeset.find_by_revision('2'), changeset.previous diff -r 261b3d9a4903 -r e248c7af89ec test/unit/comment_test.rb --- a/test/unit/comment_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/comment_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/custom_field_test.rb --- a/test/unit/custom_field_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/custom_field_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -146,6 +146,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + 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) @@ -156,6 +157,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('ABC') assert !f.valid_field_value?('abc') end @@ -165,6 +167,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + 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') @@ -175,6 +178,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('value2') assert !f.valid_field_value?('abc') end @@ -184,6 +188,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') assert f.valid_field_value?('123') assert f.valid_field_value?('+123') assert f.valid_field_value?('-123') @@ -195,6 +200,7 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + 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') @@ -206,9 +212,11 @@ assert f.valid_field_value?(nil) assert f.valid_field_value?('') + 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?([' ']) assert f.valid_field_value?('value2') assert !f.valid_field_value?('abc') diff -r 261b3d9a4903 -r e248c7af89ec test/unit/custom_field_user_format_test.rb --- a/test/unit/custom_field_user_format_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/custom_field_user_format_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/custom_field_version_format_test.rb --- a/test/unit/custom_field_version_format_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/custom_field_version_format_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/custom_value_test.rb --- a/test/unit/custom_value_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/custom_value_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/default_data_test.rb --- a/test/unit/default_data_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/default_data_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/document_category_test.rb --- a/test/unit/document_category_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/document_category_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/document_test.rb --- a/test/unit/document_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/document_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/enabled_module_test.rb --- a/test/unit/enabled_module_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/enabled_module_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/enumeration_test.rb --- a/test/unit/enumeration_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/enumeration_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/group_test.rb --- a/test/unit/group_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/group_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/activities_helper_test.rb --- a/test/unit/helpers/activities_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/activities_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/application_helper_test.rb --- a/test/unit/helpers/application_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/application_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/custom_fields_helper_test.rb --- a/test/unit/helpers/custom_fields_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/custom_fields_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/issues_helper_test.rb --- a/test/unit/helpers/issues_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/issues_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/projects_helper_test.rb --- a/test/unit/helpers/projects_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/projects_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/queries_helper_test.rb --- a/test/unit/helpers/queries_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/queries_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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,4 +36,18 @@ assert_equal filter_count + 1, fo.size assert_equal [], fo[0] end + + def test_query_to_csv_should_translate_boolean_custom_field_values + f = IssueCustomField.generate!(:field_format => 'bool', :name => 'Boolean', :is_for_all => true, :trackers => Tracker.all) + issues = [ + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '0'}), + Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '1'}) + ] + + with_locale 'fr' do + csv = query_to_csv(issues, IssueQuery.new, :columns => 'all') + assert_include "Oui", csv + assert_include "Non", csv + end + end end diff -r 261b3d9a4903 -r e248c7af89ec test/unit/helpers/search_helper_test.rb --- a/test/unit/helpers/search_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/search_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/sort_helper_test.rb --- a/test/unit/helpers/sort_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/sort_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/timelog_helper_test.rb --- a/test/unit/helpers/timelog_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/timelog_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/helpers/watchers_helper_test.rb --- a/test/unit/helpers/watchers_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/helpers/watchers_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/initializers/patches_test.rb --- a/test/unit/initializers/patches_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/initializers/patches_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_category_test.rb --- a/test/unit/issue_category_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_category_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_custom_field_test.rb --- a/test/unit/issue_custom_field_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_custom_field_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_nested_set_test.rb --- a/test/unit/issue_nested_set_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_nested_set_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -358,6 +358,18 @@ assert_equal 12, parent.reload.estimated_hours end + def test_done_ratio_of_parent_with_a_child_with_estimated_time_at_0_should_not_exceed_100 + parent = Issue.generate! + Issue.generate!(:estimated_hours => 40, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 40, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 20, :parent_issue_id => parent.id) + Issue.generate!(:estimated_hours => 0, :parent_issue_id => parent.id) + parent.reload.children.each do |child| + child.update_attribute :status_id, 5 + end + assert_equal 100, parent.reload.done_ratio + end + def test_move_parent_updates_old_parent_attributes first_parent = Issue.generate! second_parent = Issue.generate! diff -r 261b3d9a4903 -r e248c7af89ec test/unit/issue_priority_test.rb --- a/test/unit/issue_priority_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_priority_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_relation_test.rb --- a/test/unit/issue_relation_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_relation_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_status_test.rb --- a/test/unit/issue_status_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_status_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/issue_test.rb --- a/test/unit/issue_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -2202,16 +2202,19 @@ assert_include 'priority-highest', classes end - def test_css_classes_should_include_user_assignment - issue = Issue.generate(:assigned_to_id => 2) - assert_include 'assigned-to-me', issue.css_classes(User.find(2)) - assert_not_include 'assigned-to-me', issue.css_classes(User.find(3)) - end - - def test_css_classes_should_include_user_group_assignment - issue = Issue.generate(:assigned_to_id => 10) - assert_include 'assigned-to-my-group', issue.css_classes(Group.find(10).users.first) - assert_not_include 'assigned-to-my-group', issue.css_classes(User.find(3)) + def test_css_classes_should_include_user_and_group_assignment + project = Project.first + user = User.generate! + group = Group.generate! + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.users << user + assert user.member_of?(project) + issue1 = Issue.generate(:assigned_to_id => group.id) + assert_include 'assigned-to-my-group', issue1.css_classes(user) + assert_not_include 'assigned-to-me', issue1.css_classes(user) + issue2 = Issue.generate(:assigned_to_id => user.id) + assert_not_include 'assigned-to-my-group', issue2.css_classes(user) + assert_include 'assigned-to-me', issue2.css_classes(user) end def test_save_attachments_with_hash_should_save_attachments_in_keys_order diff -r 261b3d9a4903 -r e248c7af89ec test/unit/issue_transaction_test.rb --- a/test/unit/issue_transaction_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/issue_transaction_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/journal_observer_test.rb --- a/test/unit/journal_observer_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/journal_observer_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/journal_test.rb --- a/test/unit/journal_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/journal_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/access_control_test.rb --- a/test/unit/lib/redmine/access_control_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/access_control_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/ciphering_test.rb --- a/test/unit/lib/redmine/ciphering_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/ciphering_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/codeset_util_test.rb --- a/test/unit/lib/redmine/codeset_util_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/codeset_util_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/configuration_test.rb --- a/test/unit/lib/redmine/configuration_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/configuration_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/export/pdf_test.rb --- a/test/unit/lib/redmine/export/pdf_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/export/pdf_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/helpers/calendar_test.rb --- a/test/unit/lib/redmine/helpers/calendar_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/helpers/calendar_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/helpers/diff_test.rb --- a/test/unit/lib/redmine/helpers/diff_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/helpers/diff_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/helpers/gantt_test.rb --- a/test/unit/lib/redmine/helpers/gantt_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/helpers/gantt_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/hook_test.rb --- a/test/unit/lib/redmine/hook_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/hook_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/i18n_test.rb --- a/test/unit/lib/redmine/i18n_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/i18n_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/info_test.rb --- a/test/unit/lib/redmine/info_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/info_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/menu_manager/mapper_test.rb --- a/test/unit/lib/redmine/menu_manager/mapper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/menu_manager/mapper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/menu_manager/menu_helper_test.rb --- a/test/unit/lib/redmine/menu_manager/menu_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/menu_manager/menu_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/menu_manager_test.rb --- a/test/unit/lib/redmine/menu_manager_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/menu_manager_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/mime_type_test.rb --- a/test/unit/lib/redmine/mime_type_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/mime_type_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/notifiable_test.rb --- a/test/unit/lib/redmine/notifiable_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/notifiable_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/pagination_helper_test.rb --- a/test/unit/lib/redmine/pagination_helper_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/pagination_helper_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/pagination_test.rb --- a/test/unit/lib/redmine/pagination_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/pagination_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/plugin_test.rb --- a/test/unit/lib/redmine/plugin_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/plugin_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/safe_attributes_test.rb --- a/test/unit/lib/redmine/safe_attributes_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/safe_attributes_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/git_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -89,8 +89,8 @@ 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 + assert_equal '33', adp.info.lastrev.revision + assert_equal '2e6d54642923',adp.info.lastrev.scmid end end diff -r 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/themes_test.rb --- a/test/unit/lib/redmine/themes_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/themes_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/unified_diff_test.rb --- a/test/unit/lib/redmine/unified_diff_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/unified_diff_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/utils/date_calculation.rb --- a/test/unit/lib/redmine/utils/date_calculation.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/utils/date_calculation.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/views/builders/json_test.rb --- a/test/unit/lib/redmine/views/builders/json_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/views/builders/json_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/views/builders/xml_test.rb --- a/test/unit/lib/redmine/views/builders/xml_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/views/builders/xml_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/wiki_formatting/macros_test.rb --- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb --- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine/wiki_formatting_test.rb --- a/test/unit/lib/redmine/wiki_formatting_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine/wiki_formatting_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/lib/redmine_test.rb --- a/test/unit/lib/redmine_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/lib/redmine_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/mail_handler_test.rb --- a/test/unit/mail_handler_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/mail_handler_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/mailer_test.rb --- a/test/unit/mailer_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/mailer_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/member_test.rb --- a/test/unit/member_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/member_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/message_test.rb --- a/test/unit/message_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/message_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/news_test.rb --- a/test/unit/news_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/news_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/principal_test.rb --- a/test/unit/principal_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/principal_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/project_copy_test.rb --- a/test/unit/project_copy_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/project_copy_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/project_members_inheritance_test.rb --- a/test/unit/project_members_inheritance_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/project_members_inheritance_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/project_nested_set_test.rb --- a/test/unit/project_nested_set_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/project_nested_set_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/project_test.rb --- a/test/unit/project_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/project_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/query_test.rb --- a/test/unit/query_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/query_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_bazaar_test.rb --- a/test/unit/repository_bazaar_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_bazaar_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_cvs_test.rb --- a/test/unit/repository_cvs_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_cvs_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_darcs_test.rb --- a/test/unit/repository_darcs_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_darcs_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_filesystem_test.rb --- a/test/unit/repository_filesystem_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_filesystem_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_git_test.rb --- a/test/unit/repository_git_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_git_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_mercurial_test.rb --- a/test/unit/repository_mercurial_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_mercurial_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 @@ include Redmine::I18n REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s - NUM_REV = 32 + NUM_REV = 34 CHAR_1_HEX = "\xc3\x9c" def setup @@ -144,7 +144,7 @@ # with_limit changesets = @repository.latest_changesets('', nil, 2) - assert_equal %w|31 30|, changesets.collect(&:revision) + assert_equal ["#{NUM_REV - 1}", "#{NUM_REV - 2}"], changesets.collect(&:revision) # with_filepath changesets = @repository.latest_changesets( @@ -365,7 +365,7 @@ @repository.fetch_changesets @project.reload assert_equal NUM_REV, @repository.changesets.count - %w|31 31eeee7395c8 31eee|.each do |r1| + ["#{NUM_REV - 1}", "2e6d54642923", "2e6d5"].each do |r1| changeset = @repository.find_changeset_by_name(r1) assert_nil changeset.next end diff -r 261b3d9a4903 -r e248c7af89ec test/unit/repository_subversion_test.rb --- a/test/unit/repository_subversion_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_subversion_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/repository_test.rb --- a/test/unit/repository_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/repository_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -326,6 +326,12 @@ assert_equal true, klass.scm_available end + def test_extra_info_should_not_return_non_hash_value + repo = Repository.new + repo.extra_info = "foo" + assert_nil repo.extra_info + end + def test_merge_extra_info repo = Repository::Subversion.new(:project => Project.find(3)) assert !repo.save diff -r 261b3d9a4903 -r e248c7af89ec test/unit/role_test.rb --- a/test/unit/role_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/role_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/search_test.rb --- a/test/unit/search_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/search_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/setting_test.rb --- a/test/unit/setting_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/setting_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/time_entry_activity_test.rb --- a/test/unit/time_entry_activity_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/time_entry_activity_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/time_entry_query_test.rb --- a/test/unit/time_entry_query_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/time_entry_query_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/time_entry_test.rb --- a/test/unit/time_entry_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/time_entry_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/token_test.rb --- a/test/unit/token_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/token_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/user_preference_test.rb --- a/test/unit/user_preference_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/user_preference_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/user_test.rb --- a/test/unit/user_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/user_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/version_test.rb --- a/test/unit/version_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/version_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/watcher_test.rb --- a/test/unit/watcher_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/watcher_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/wiki_content_test.rb --- a/test/unit/wiki_content_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/wiki_content_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/wiki_content_version_test.rb --- a/test/unit/wiki_content_version_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/wiki_content_version_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/wiki_page_test.rb --- a/test/unit/wiki_page_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/wiki_page_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/wiki_redirect_test.rb --- a/test/unit/wiki_redirect_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/wiki_redirect_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/wiki_test.rb --- a/test/unit/wiki_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/wiki_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-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 261b3d9a4903 -r e248c7af89ec test/unit/workflow_test.rb --- a/test/unit/workflow_test.rb Tue Jan 14 14:37:42 2014 +0000 +++ b/test/unit/workflow_test.rb Mon Mar 17 08:54:02 2014 +0000 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2006-2014 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License