# HG changeset patch # User Chris Cannam # Date 1410251311 -3600 # Node ID b450a9d58aedc637ab24eff22ddec55048cd01c2 # Parent e248c7af89ecdc5a4c5bedafdb7c6d357643873e Update to Redmine SVN revision 13356 on 2.4-stable branch diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/00/0009a621965fc195e93ba67a7d3500cbcd38b084.svn-base --- a/.svn/pristine/00/0009a621965fc195e93ba67a7d3500cbcd38b084.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/01/019777b51c435b18c756e5573fde25c903195b68.svn-base --- a/.svn/pristine/01/019777b51c435b18c756e5573fde25c903195b68.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -require 'rexml/document' - -module Redmine - module VERSION #:nodoc: - MAJOR = 2 - MINOR = 4 - TINY = 2 - - # Branch values: - # * official release: nil - # * stable branch: stable - # * trunk: devel - BRANCH = 'stable' - - # Retrieves the revision from the working copy - def self.revision - if File.directory?(File.join(Rails.root, '.svn')) - begin - path = Redmine::Scm::Adapters::AbstractAdapter.shell_quote(Rails.root.to_s) - if `svn info --xml #{path}` =~ /revision="(\d+)"/ - return $1.to_i - end - rescue - # Could not find the current revision - end - end - nil - end - - REVISION = self.revision - ARRAY = [MAJOR, MINOR, TINY, BRANCH, REVISION].compact - STRING = ARRAY.join('.') - - def self.to_a; ARRAY end - def self.to_s; STRING end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/01/01b21d04c9e912327dc0059ef282ceb1a603f54f.svn-base --- a/.svn/pristine/01/01b21d04c9e912327dc0059ef282ceb1a603f54f.svn-base Mon Mar 17 08:54:02 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 AutoCompletesControllerTest < ActionController::TestCase - fixtures :projects, :issues, :issue_statuses, - :enumerations, :users, :issue_categories, - :trackers, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :journals, :journal_details - - def test_issues_should_not_be_case_sensitive - get :issues, :project_id => 'ecookbook', :q => 'ReCiPe' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} - end - - def test_issues_should_accept_term_param - get :issues, :project_id => 'ecookbook', :term => 'ReCiPe' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} - end - - def test_issues_should_return_issue_with_given_id - get :issues, :project_id => 'subproject1', :q => '13' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_issues_should_return_issue_with_given_id_preceded_with_hash - get :issues, :project_id => 'subproject1', :q => '#13' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_with_scope_all_should_search_other_projects - get :issues, :project_id => 'ecookbook', :q => '13', :scope => 'all' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_without_project_should_search_all_projects - get :issues, :q => '13' - assert_response :success - assert_not_nil assigns(:issues) - assert assigns(:issues).include?(Issue.find(13)) - end - - def test_auto_complete_without_scope_all_should_not_search_other_projects - get :issues, :project_id => 'ecookbook', :q => '13' - assert_response :success - assert_equal [], assigns(:issues) - end - - def test_issues_should_return_json - get :issues, :project_id => 'subproject1', :q => '13' - assert_response :success - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Array, json - issue = json.first - assert_kind_of Hash, issue - assert_equal 13, issue['id'] - assert_equal 13, issue['value'] - assert_equal 'Bug #13: Subproject issue two', issue['label'] - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/01/01cfbfdc38592dff3cbe5b1b36118aa0e1e65223.svn-base --- a/.svn/pristine/01/01cfbfdc38592dff3cbe5b1b36118aa0e1e65223.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +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 GanttsControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :versions - - def test_gantt_should_work - i2 = Issue.find(2) - i2.update_attribute(:due_date, 1.month.from_now) - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - # Issue with start and due dates - i = Issue.find(1) - assert_not_nil i.due_date - assert_select "div a.issue", /##{i.id}/ - # Issue with on a targeted version should not be in the events but loaded in the html - i = Issue.find(2) - assert_select "div a.issue", /##{i.id}/ - end - - def test_gantt_should_work_without_issue_due_dates - Issue.update_all("due_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_without_issue_and_version_due_dates - Issue.update_all("due_date = NULL") - Version.update_all("effective_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_cross_project - get :show - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - assert_not_nil assigns(:gantt).query - assert_nil assigns(:gantt).project - end - - def test_gantt_should_not_disclose_private_projects - get :show - assert_response :success - assert_template 'gantts/show' - assert_tag 'a', :content => /eCookbook/ - # Root private project - assert_no_tag 'a', {:content => /OnlineStore/} - # Private children of a public project - assert_no_tag 'a', :content => /Private child of eCookbook/ - end - - def test_gantt_should_display_relations - IssueRelation.delete_all - issue1 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) - issue2 = Issue.generate!(:start_date => 1.day.from_now.to_date, :due_date => 3.day.from_now.to_date) - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') - - get :show - assert_response :success - - relations = assigns(:gantt).relations - assert_kind_of Hash, relations - assert relations.present? - assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s - assert_select 'div.task_todo[id=?]:not([data-rels])', "task-todo-issue-#{issue2.id}" - end - - def test_gantt_should_export_to_pdf - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_export_to_pdf_cross_project - get :show, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - if Object.const_defined?(:Magick) - def test_gantt_should_export_to_png - get :show, :project_id => 1, :format => 'png' - assert_response :success - assert_equal 'image/png', @response.content_type - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/02/027410b40ae4e2d4f4be0368cf13b4cdb0164b5f.svn-base --- a/.svn/pristine/02/027410b40ae4e2d4f4be0368cf13b4cdb0164b5f.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +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 - - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/02/02fe391dd14e50d29ad45f8bf8dea30be02556c5.svn-base --- a/.svn/pristine/02/02fe391dd14e50d29ad45f8bf8dea30be02556c5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +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 Themes - - # Return an array of installed themes - def self.themes - @@installed_themes ||= scan_themes - end - - # Rescan themes directory - def self.rescan - @@installed_themes = scan_themes - end - - # Return theme for given id, or nil if it's not found - def self.theme(id, options={}) - return nil if id.blank? - - found = themes.find {|t| t.id == id} - if found.nil? && options[:rescan] != false - rescan - found = theme(id, :rescan => false) - end - found - end - - # Class used to represent a theme - class Theme - attr_reader :path, :name, :dir - - def initialize(path) - @path = path - @dir = File.basename(path) - @name = @dir.humanize - @stylesheets = nil - @javascripts = nil - end - - # Directory name used as the theme id - def id; dir end - - def ==(theme) - theme.is_a?(Theme) && theme.dir == dir - end - - def <=>(theme) - name <=> theme.name - end - - def stylesheets - @stylesheets ||= assets("stylesheets", "css") - end - - def images - @images ||= assets("images") - end - - def javascripts - @javascripts ||= assets("javascripts", "js") - end - - def stylesheet_path(source) - "/themes/#{dir}/stylesheets/#{source}" - end - - def image_path(source) - "/themes/#{dir}/images/#{source}" - end - - def javascript_path(source) - "/themes/#{dir}/javascripts/#{source}" - end - - private - - def assets(dir, ext=nil) - if ext - Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')} - else - Dir.glob("#{path}/#{dir}/*").collect {|f| File.basename(f)} - end - end - end - - private - - def self.scan_themes - dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f| - # A theme should at least override application.css - File.directory?(f) && File.exist?("#{f}/stylesheets/application.css") - end - dirs.collect {|dir| Theme.new(dir)}.sort - end - end -end - -module ApplicationHelper - def current_theme - unless instance_variable_defined?(:@current_theme) - @current_theme = Redmine::Themes.theme(Setting.ui_theme) - end - @current_theme - end - - # Returns the header tags for the current theme - def heads_for_theme - if current_theme && current_theme.javascripts.include?('theme') - javascript_include_tag current_theme.javascript_path('theme') - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/03/0335a712dd5fb2c44f8cb6daaf182ac72aee4c7f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/0335a712dd5fb2c44f8cb6daaf182ac72aee4c7f.svn-base Tue Sep 09 09:28:31 2014 +0100 @@ -0,0 +1,287 @@ + + + +RedmineWikiFormatting + + + + + +

Wiki 格式設定

+ +

連結

+ +

Redmine 連結

+ +

在任何可以使用 Wiki 格式設定的地方, Redmine 都允許在資源(問題、變更集、 Wiki 頁面...)間建立超連結。

+ + +

Wiki 連結:

+ + + +

您也可以連結至其他專案的 Wiki 頁面:

+ + + +

當頁面不存在的時候, Wiki 連結會以紅色的方式顯示,例如: Nonexistent page.

+ +

連結至其他資源:

+ + + + + + + + + + + + + + + + +

逸出字元:

+ + + + +

外部連結

+ +

HTTP URLs 與電子郵件地址會自動被轉換成可被點擊的連結:

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

顯示為: http://www.redmine.org, someone@foo.bar

+ +

若您想要顯示指定的文字而非該 URL ,您可以使用下列標準的 textile 語法:

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

顯示為: Redmine web site

+ + +

文字格式設定

+ + +

對於諸如標題、粗體、表格、清單等項目, Redmine 支援使用 Textile 語法。可參考 http://en.wikipedia.org/wiki/Textile_(markup_language) 中關於使用這些格式化功能的說明資訊。 下面包含了一些使用範例,但格式化引擎的處理能力遠多於這些簡單的使用範例。

+ +

字型樣式

+ +
+* *粗體*
+* _斜體_
+* _*粗斜體*_
+* +底線+
+* -刪除線-
+
+ +

顯示為:

+ + + +

內置圖像

+ + + +

標題

+ +
+h1. 標題
+h2. 次標題
+h3. 次次標題
+
+ +

Redmine 為每一種標題指定一個 HTML 錨定 (anchor) ,因此您可使用 "#Heading" 、 "#Subheading" 等方式連結至這些標題。

+ + +

段落

+ +
+p>. 靠右對齊
+p=. 置中對齊
+
+ +

這是一個置中對齊的段落。

+ + +

引用文字

+ +

使用 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.
+
+ +

顯示為:

+ +
+

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.

+
+ + +

目錄

+ +
+{{toc}} => 靠左對齊目錄
+{{>toc}} => 靠右對齊目錄
+
+ +

水平線

+ +
+---
+
+ +

巨集

+ +

Redmine 內建下列巨集:

+ +

hello_world

範例巨集。

include

引入一個 wiki 頁面。例子:

+ +
{{include(Foo)}}
macro_list

顯示所有可用巨集的清單,若巨集有提供說明也會一併顯示。

+ + +

程式碼醒目提示

+ +

預設使用 CodeRay 作為程式碼醒目提示的機制,它是一個使用 Ruby 撰寫的語法醒目提示函式庫。它目前支援 c 、 cpp 、 css 、 delphi 、 groovy 、 html 、 java 、 javascript 、 json 、 php 、 python 、 rhtml 、 ruby 、 scheme 、 sql 、 xml 與 yaml 等程式語言。

+ +

您可以使用下列語法,在 Wiki 頁面中將程式碼標示為醒目提示:

+ +
+<pre><code class="ruby">
+  將程式碼放在這裡。
+</code></pre>
+
+ +

例子:

+ +
 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 e248c7af89ec -r b450a9d58aed .svn/pristine/03/033d1080f94d03c5b655f6847c2f7606bd5d5f60.svn-base --- a/.svn/pristine/03/033d1080f94d03c5b655f6847c2f7606bd5d5f60.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 e248c7af89ec -r b450a9d58aed .svn/pristine/04/0490719dc3185896fb07e210967a75c0f5e40a0a.svn-base --- a/.svn/pristine/04/0490719dc3185896fb07e210967a75c0f5e40a0a.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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 IssueTransactionTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :trackers, :projects_trackers, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - self.use_transactional_fixtures = false - - def test_invalid_move_to_another_project - parent1 = Issue.generate! - child = Issue.generate!(:parent_issue_id => parent1.id) - grandchild = Issue.generate!(:parent_issue_id => child.id, :tracker_id => 2) - Project.find(2).tracker_ids = [1] - - parent1.reload - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - - # child can not be moved to Project 2 because its child is on a disabled tracker - child = Issue.find(child.id) - child.project = Project.find(2) - assert !child.save - child.reload - grandchild.reload - parent1.reload - - # no change - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt] - assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/04/049234b460fae5eb30eba8a2487d0eac09c6701f.svn-base --- a/.svn/pristine/04/049234b460fae5eb30eba8a2487d0eac09c6701f.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +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. - -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 e248c7af89ec -r b450a9d58aed .svn/pristine/05/05d8cde4a4e9772f7dcbafb481eff20153ca4409.svn-base --- a/.svn/pristine/05/05d8cde4a4e9772f7dcbafb481eff20153ca4409.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/05/05fb4e3ebed828cf639f80e5d1d6da6095d43000.svn-base --- a/.svn/pristine/05/05fb4e3ebed828cf639f80e5d1d6da6095d43000.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/06/06906d5d51f832720062a81972796df1a81f69a3.svn-base --- a/.svn/pristine/06/06906d5d51f832720062a81972796df1a81f69a3.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +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 AdminControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_select 'div.nodata', 0 - end - - def test_index_with_no_configuration_data - delete_configuration_data - get :index - assert_select 'div.nodata' - end - - def test_projects - get :projects - assert_response :success - assert_template 'projects' - assert_not_nil assigns(:projects) - # active projects only - assert_nil assigns(:projects).detect {|u| !u.active?} - end - - def test_projects_with_status_filter - get :projects, :status => 1 - assert_response :success - assert_template 'projects' - assert_not_nil assigns(:projects) - # active projects only - assert_nil assigns(:projects).detect {|u| !u.active?} - end - - def test_projects_with_name_filter - get :projects, :name => 'store', :status => '' - assert_response :success - assert_template 'projects' - projects = assigns(:projects) - assert_not_nil projects - assert_equal 1, projects.size - assert_equal 'OnlineStore', projects.first.name - end - - def test_load_default_configuration_data - delete_configuration_data - post :default_configuration, :lang => 'fr' - assert_response :redirect - assert_nil flash[:error] - assert IssueStatus.find_by_name('Nouveau') - end - - def test_load_default_configuration_data_should_rescue_error - delete_configuration_data - Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong")) - post :default_configuration, :lang => 'fr' - assert_response :redirect - assert_not_nil flash[:error] - assert_match /Something went wrong/, flash[:error] - end - - def test_test_email - user = User.find(1) - user.pref.no_self_notified = '1' - user.pref.save! - ActionMailer::Base.deliveries.clear - - get :test_email - assert_redirected_to '/settings?tab=notifications' - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - user = User.find(1) - assert_equal [user.mail], mail.bcc - end - - def test_test_email_failure_should_display_the_error - Mailer.stubs(:test_email).raises(Exception, 'Some error message') - get :test_email - assert_redirected_to '/settings?tab=notifications' - assert_match /Some error message/, flash[:error] - end - - def test_no_plugins - Redmine::Plugin.clear - - get :plugins - assert_response :success - assert_template 'plugins' - end - - def test_plugins - # Register a few plugins - Redmine::Plugin.register :foo do - name 'Foo plugin' - author 'John Smith' - description 'This is a test plugin' - version '0.0.1' - settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings' - end - Redmine::Plugin.register :bar do - end - - get :plugins - assert_response :success - assert_template 'plugins' - - assert_select 'tr#plugin-foo' do - assert_select 'td span.name', :text => 'Foo plugin' - assert_select 'td.configure a[href=/settings/plugin/foo]' - end - assert_select 'tr#plugin-bar' do - assert_select 'td span.name', :text => 'Bar' - assert_select 'td.configure a', 0 - end - end - - def test_info - get :info - assert_response :success - assert_template 'info' - end - - def test_admin_menu_plugin_extension - Redmine::MenuManager.map :admin_menu do |menu| - menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test' - end - - get :index - assert_response :success - assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test' - - Redmine::MenuManager.map :admin_menu do |menu| - menu.delete :test_admin_menu_plugin_extension - end - end - - private - - def delete_configuration_data - Role.delete_all('builtin = 0') - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/06/06927559eed548b89dcc19d7fe7d3d26046b88c1.svn-base --- a/.svn/pristine/06/06927559eed548b89dcc19d7fe7d3d26046b88c1.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,428 +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 IssuesHelper - include ApplicationHelper - - def issue_list(issues, &block) - ancestors = [] - issues.each do |issue| - while (ancestors.any? && !issue.is_descendant_of?(ancestors.last)) - ancestors.pop - end - yield issue, ancestors.size - ancestors << issue unless issue.leaf? - end - end - - # Renders a HTML/CSS tooltip - # - # To use, a trigger div is needed. This is a div with the class of "tooltip" - # that contains this method wrapped in a span with the class of "tip" - # - #
<%= link_to_issue(issue) %> - # <%= render_issue_tooltip(issue) %> - #
- # - def render_issue_tooltip(issue) - @cached_label_status ||= l(:field_status) - @cached_label_start_date ||= l(:field_start_date) - @cached_label_due_date ||= l(:field_due_date) - @cached_label_assigned_to ||= l(:field_assigned_to) - @cached_label_priority ||= l(:field_priority) - @cached_label_project ||= l(:field_project) - - link_to_issue(issue) + "

".html_safe + - "#{@cached_label_project}: #{link_to_project(issue.project)}
".html_safe + - "#{@cached_label_status}: #{h(issue.status.name)}
".html_safe + - "#{@cached_label_start_date}: #{format_date(issue.start_date)}
".html_safe + - "#{@cached_label_due_date}: #{format_date(issue.due_date)}
".html_safe + - "#{@cached_label_assigned_to}: #{h(issue.assigned_to)}
".html_safe + - "#{@cached_label_priority}: #{h(issue.priority.name)}".html_safe - end - - def issue_heading(issue) - h("#{issue.tracker} ##{issue.id}") - end - - def render_issue_subject_with_tree(issue) - s = '' - ancestors = issue.root? ? [] : issue.ancestors.visible.all - ancestors.each do |ancestor| - s << '
' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id))) - end - s << '
' - subject = h(issue.subject) - if issue.is_private? - subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject - end - s << content_tag('h3', subject) - s << '
' * (ancestors.size + 1) - s.html_safe - end - - def render_descendants_tree(issue) - s = '
' - issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| - css = "issue issue-#{child.id} hascontextmenu" - css << " idnt idnt-#{level}" if level > 0 - s << content_tag('tr', - content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') + - content_tag('td', link_to_issue(child, :truncate => 60, :project => (issue.project_id != child.project_id)), :class => 'subject') + - content_tag('td', h(child.status)) + - content_tag('td', link_to_user(child.assigned_to)) + - content_tag('td', progress_bar(child.done_ratio, :width => '80px')), - :class => css) - end - s << '
' - s.html_safe - end - - # Returns an array of error messages for bulk edited issues - def bulk_edit_error_messages(issues) - messages = {} - issues.each do |issue| - issue.errors.full_messages.each do |message| - messages[message] ||= [] - messages[message] << issue - end - end - messages.map { |message, issues| - "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ') - } - end - - # Returns a link for adding a new subtask to the given issue - def link_to_new_subtask(issue) - attrs = { - :tracker_id => issue.tracker, - :parent_issue_id => issue - } - link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs)) - end - - class IssueFieldsRows - include ActionView::Helpers::TagHelper - - def initialize - @left = [] - @right = [] - end - - def left(*args) - args.any? ? @left << cells(*args) : @left - end - - def right(*args) - args.any? ? @right << cells(*args) : @right - end - - def size - @left.size > @right.size ? @left.size : @right.size - end - - def to_html - html = ''.html_safe - blank = content_tag('th', '') + content_tag('td', '') - size.times do |i| - left = @left[i] || blank - right = @right[i] || blank - html << content_tag('tr', left + right) - end - html - end - - def cells(label, text, options={}) - content_tag('th', "#{label}:", options) + content_tag('td', text, options) - end - end - - def issue_fields_rows - r = IssueFieldsRows.new - yield r - r.to_html - end - - def render_custom_fields_rows(issue) - values = issue.visible_custom_field_values - return if values.empty? - ordered_values = [] - half = (values.size / 2.0).ceil - half.times do |i| - ordered_values << values[i] - ordered_values << values[i + half] - end - s = "\n" - n = 0 - ordered_values.compact.each do |value| - s << "\n\n" if n > 0 && (n % 2) == 0 - s << "\t#{ h(value.custom_field.name) }:#{ simple_format_without_paragraph(h(show_value(value))) }\n" - n += 1 - end - s << "\n" - s.html_safe - end - - def issues_destroy_confirmation_message(issues) - issues = [issues] unless issues.is_a?(Array) - message = l(:text_issues_destroy_confirmation) - descendant_count = issues.inject(0) {|memo, i| memo += (i.right - i.left - 1)/2} - if descendant_count > 0 - issues.each do |issue| - next if issue.root? - issues.each do |other_issue| - descendant_count -= 1 if issue.is_descendant_of?(other_issue) - end - end - if descendant_count > 0 - message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count) - end - end - message - end - - def sidebar_queries - unless @sidebar_queries - @sidebar_queries = IssueQuery.visible. - order("#{Query.table_name}.name ASC"). - # Project specific queries and global queries - where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]). - all - end - @sidebar_queries - end - - def query_links(title, queries) - return '' if queries.empty? - # links to #index on issues/show - url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params - - content_tag('h3', title) + "\n" + - content_tag('ul', - queries.collect {|query| - css = 'query' - css << ' selected' if query == @query - content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css)) - }.join("\n").html_safe, - :class => 'queries' - ) + "\n" - end - - def render_sidebar_queries - out = ''.html_safe - out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?)) - out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?)) - out - end - - def email_issue_attributes(issue, user) - items = [] - %w(author status priority assigned_to category fixed_version).each do |attribute| - unless issue.disabled_core_fields.include?(attribute+"_id") - items << "#{l("field_#{attribute}")}: #{issue.send attribute}" - end - end - issue.visible_custom_field_values(user).each do |value| - items << "#{value.custom_field.name}: #{show_value(value)}" - end - items - end - - def render_email_issue_attributes(issue, user, html=false) - items = email_issue_attributes(issue, user) - if html - content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe) - else - items.map{|s| "* #{s}"}.join("\n") - end - end - - # Returns the textual representation of a journal details - # as an array of strings - def details_to_strings(details, no_html=false, options={}) - options[:only_path] = (options[:only_path] == false ? false : true) - strings = [] - values_by_field = {} - details.each do |detail| - if detail.property == 'cf' - field = detail.custom_field - if field && field.multiple? - values_by_field[field] ||= {:added => [], :deleted => []} - if detail.old_value - values_by_field[field][:deleted] << detail.old_value - end - if detail.value - values_by_field[field][:added] << detail.value - end - next - end - end - strings << show_detail(detail, no_html, options) - end - values_by_field.each do |field, changes| - detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) - detail.instance_variable_set "@custom_field", field - if changes[:added].any? - detail.value = changes[:added] - strings << show_detail(detail, no_html, options) - elsif changes[:deleted].any? - detail.old_value = changes[:deleted] - strings << show_detail(detail, no_html, options) - end - end - strings - end - - # Returns the textual representation of a single journal detail - def show_detail(detail, no_html=false, options={}) - multiple = false - case detail.property - when 'attr' - field = detail.prop_key.to_s.gsub(/\_id$/, "") - label = l(("field_" + field).to_sym) - case detail.prop_key - when 'due_date', 'start_date' - value = format_date(detail.value.to_date) if detail.value - old_value = format_date(detail.old_value.to_date) if detail.old_value - - when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', - 'priority_id', 'category_id', 'fixed_version_id' - value = find_name_by_reflection(field, detail.value) - old_value = find_name_by_reflection(field, detail.old_value) - - when 'estimated_hours' - value = "%0.02f" % detail.value.to_f unless detail.value.blank? - old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? - - when 'parent_id' - label = l(:field_parent_issue) - value = "##{detail.value}" unless detail.value.blank? - old_value = "##{detail.old_value}" unless detail.old_value.blank? - - when 'is_private' - value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank? - old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? - end - when 'cf' - custom_field = detail.custom_field - if custom_field - multiple = custom_field.multiple? - label = custom_field.name - value = format_value(detail.value, custom_field.field_format) if detail.value - old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value - end - when 'attachment' - label = l(:label_attachment) - when 'relation' - if detail.value && !detail.old_value - rel_issue = Issue.visible.find_by_id(detail.value) - value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" : - (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) - elsif detail.old_value && !detail.value - rel_issue = Issue.visible.find_by_id(detail.old_value) - old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" : - (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) - end - label = l(detail.prop_key.to_sym) - end - call_hook(:helper_issues_show_detail_after_setting, - {:detail => detail, :label => label, :value => value, :old_value => old_value }) - - label ||= detail.prop_key - value ||= detail.value - old_value ||= detail.old_value - - unless no_html - label = content_tag('strong', label) - old_value = content_tag("i", h(old_value)) if detail.old_value - if detail.old_value && detail.value.blank? && detail.property != 'relation' - old_value = content_tag("del", old_value) - end - if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) - # Link to the attachment if it has not been removed - value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) - if options[:only_path] != false && atta.is_text? - value += link_to( - image_tag('magnifier.png'), - :controller => 'attachments', :action => 'show', - :id => atta, :filename => atta.filename - ) - end - else - value = content_tag("i", h(value)) if value - end - end - - if detail.property == 'attr' && detail.prop_key == 'description' - s = l(:text_journal_changed_no_detail, :label => label) - unless no_html - diff_link = link_to 'diff', - {:controller => 'journals', :action => 'diff', :id => detail.journal_id, - :detail_id => detail.id, :only_path => options[:only_path]}, - :title => l(:label_view_diff) - s << " (#{ diff_link })" - end - s.html_safe - elsif detail.value.present? - case detail.property - when 'attr', 'cf' - if detail.old_value.present? - l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe - elsif multiple - l(:text_journal_added, :label => label, :value => value).html_safe - else - l(:text_journal_set_to, :label => label, :value => value).html_safe - end - when 'attachment', 'relation' - l(:text_journal_added, :label => label, :value => value).html_safe - end - else - l(:text_journal_deleted, :label => label, :old => old_value).html_safe - end - end - - # Find the name of an associated record stored in the field attribute - def find_name_by_reflection(field, id) - unless id.present? - return nil - end - association = Issue.reflect_on_association(field.to_sym) - if association - record = association.class_name.constantize.find_by_id(id) - if record - record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding) - return record.name - end - end - end - - # Renders issue children recursively - def render_api_issue_children(issue, api) - return if issue.leaf? - api.array :children do - issue.children.each do |child| - api.issue(:id => child.id) do - api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil? - api.subject child.subject - render_api_issue_children(child, api) - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/06/06efa9c81bf4ffa47e3211023e46fcb09d4eac0f.svn-base --- a/.svn/pristine/06/06efa9c81bf4ffa47e3211023e46fcb09d4eac0f.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,216 +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 RolesControllerTest < ActionController::TestCase - fixtures :roles, :users, :members, :member_roles, :workflows, :trackers - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - - assert_not_nil assigns(:roles) - assert_equal Role.order('builtin, position').all, assigns(:roles) - - assert_tag :tag => 'a', :attributes => { :href => '/roles/1/edit' }, - :content => 'Manager' - end - - def test_new - get :new - assert_response :success - assert_template 'new' - end - - def test_new_with_copy - copy_from = Role.find(2) - - get :new, :copy => copy_from.id.to_s - assert_response :success - assert_template 'new' - - role = assigns(:role) - assert_equal copy_from.permissions, role.permissions - - assert_select 'form' do - # blank name - assert_select 'input[name=?][value=]', 'role[name]' - # edit_project permission checked - assert_select 'input[type=checkbox][name=?][value=edit_project][checked=checked]', 'role[permissions][]' - # add_project permission not checked - assert_select 'input[type=checkbox][name=?][value=add_project]', 'role[permissions][]' - assert_select 'input[type=checkbox][name=?][value=add_project][checked=checked]', 'role[permissions][]', 0 - # workflow copy selected - assert_select 'select[name=?]', 'copy_workflow_from' do - assert_select 'option[value=2][selected=selected]' - end - end - end - - def test_create_with_validaton_failure - post :create, :role => {:name => '', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'} - - assert_response :success - assert_template 'new' - assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' } - end - - def test_create_without_workflow_copy - post :create, :role => {:name => 'RoleWithoutWorkflowCopy', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'} - - assert_redirected_to '/roles' - role = Role.find_by_name('RoleWithoutWorkflowCopy') - assert_not_nil role - assert_equal [:add_issues, :edit_issues, :log_time], role.permissions - assert !role.assignable? - end - - def test_create_with_workflow_copy - post :create, :role => {:name => 'RoleWithWorkflowCopy', - :permissions => ['add_issues', 'edit_issues', 'log_time', ''], - :assignable => '0'}, - :copy_workflow_from => '1' - - assert_redirected_to '/roles' - role = Role.find_by_name('RoleWithWorkflowCopy') - assert_not_nil role - assert_equal Role.find(1).workflow_rules.size, role.workflow_rules.size - end - - def test_edit - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - assert_equal Role.find(1), assigns(:role) - assert_select 'select[name=?]', 'role[issues_visibility]' - end - - def test_edit_anonymous - get :edit, :id => Role.anonymous.id - assert_response :success - assert_template 'edit' - assert_select 'select[name=?]', 'role[issues_visibility]', 0 - end - - def test_edit_invalid_should_respond_with_404 - get :edit, :id => 999 - assert_response 404 - end - - def test_update - put :update, :id => 1, - :role => {:name => 'Manager', - :permissions => ['edit_project', ''], - :assignable => '0'} - - assert_redirected_to '/roles' - role = Role.find(1) - assert_equal [:edit_project], role.permissions - end - - def test_update_with_failure - put :update, :id => 1, :role => {:name => ''} - assert_response :success - assert_template 'edit' - end - - def test_destroy - r = Role.create!(:name => 'ToBeDestroyed', :permissions => [:view_wiki_pages]) - - delete :destroy, :id => r - assert_redirected_to '/roles' - assert_nil Role.find_by_id(r.id) - end - - def test_destroy_role_in_use - delete :destroy, :id => 1 - assert_redirected_to '/roles' - assert_equal 'This role is in use and cannot be deleted.', flash[:error] - assert_not_nil Role.find_by_id(1) - end - - def test_get_permissions - get :permissions - assert_response :success - assert_template 'permissions' - - assert_not_nil assigns(:roles) - assert_equal Role.order('builtin, position').all, assigns(:roles) - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'permissions[3][]', - :value => 'add_issues', - :checked => 'checked' } - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'permissions[3][]', - :value => 'delete_issues', - :checked => nil } - end - - def test_post_permissions - post :permissions, :permissions => { '0' => '', '1' => ['edit_issues'], '3' => ['add_issues', 'delete_issues']} - assert_redirected_to '/roles' - - assert_equal [:edit_issues], Role.find(1).permissions - assert_equal [:add_issues, :delete_issues], Role.find(3).permissions - assert Role.find(2).permissions.empty? - end - - def test_clear_all_permissions - post :permissions, :permissions => { '0' => '' } - assert_redirected_to '/roles' - assert Role.find(1).permissions.empty? - end - - def test_move_highest - put :update, :id => 3, :role => {:move_to => 'highest'} - assert_redirected_to '/roles' - assert_equal 1, Role.find(3).position - end - - def test_move_higher - position = Role.find(3).position - put :update, :id => 3, :role => {:move_to => 'higher'} - assert_redirected_to '/roles' - assert_equal position - 1, Role.find(3).position - end - - def test_move_lower - position = Role.find(2).position - put :update, :id => 2, :role => {:move_to => 'lower'} - assert_redirected_to '/roles' - assert_equal position + 1, Role.find(2).position - end - - def test_move_lowest - put :update, :id => 2, :role => {:move_to => 'lowest'} - assert_redirected_to '/roles' - assert_equal Role.count, Role.find(2).position - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/07/07ff3aff4af2ca5266644ab7e838820cda8c8752.svn-base --- a/.svn/pristine/07/07ff3aff4af2ca5266644ab7e838820cda8c8752.svn-base Mon Mar 17 08:54:02 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingAutoCompletesTest < ActionController::IntegrationTest - def test_auto_completes - assert_routing( - { :method => 'get', :path => "/issues/auto_complete" }, - { :controller => 'auto_completes', :action => 'issues' } - ) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/08/086f4bbd984edf33971e918b1e5330fe445e9dd7.svn-base --- a/.svn/pristine/08/086f4bbd984edf33971e918b1e5330fe445e9dd7.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +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::MenuManager::MenuHelperTest < ActionView::TestCase - - include Redmine::MenuManager::MenuHelper - include ERB::Util - fixtures :users, :members, :projects, :enabled_modules, :roles, :member_roles - - def setup - setup_with_controller - # Stub the current menu item in the controller - def current_menu_item - :index - end - end - - context "MenuManager#current_menu_item" do - should "be tested" - end - - context "MenuManager#render_main_menu" do - should "be tested" - end - - context "MenuManager#render_menu" do - should "be tested" - end - - context "MenuManager#menu_item_and_children" do - should "be tested" - end - - context "MenuManager#extract_node_details" do - should "be tested" - end - - def test_render_single_menu_node - node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { }) - @output_buffer = render_single_menu_node(node, 'This is a test', node.url, false) - - assert_select("a.testing", "This is a test") - end - - def test_render_menu_node - single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { }) - @output_buffer = render_menu_node(single_node, nil) - - assert_select("li") do - assert_select("a.single-node", "Single node") - end - end - - def test_render_menu_node_with_nested_items - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { }) - parent_node << - Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) << - Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { }) - - @output_buffer = render_menu_node(parent_node, nil) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-one-node", "Child one node") - assert_select("li a.child-two-node", "Child two node") - assert_select("li") do - assert_select("a.child-three-node", "Child three node") - assert_select("ul") do - assert_select("li a.child-three-inner-node", "Child three inner node") - end - end - end - end - - end - - def test_render_menu_node_with_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", - {:controller => 'issues', :action => 'index'}, - {}) - end - children - } - }) - @output_buffer = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_nested_items_and_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - parent_node << Redmine::MenuManager::MenuItem.new(:child_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 6.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - @output_buffer = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-node", "Child node") - assert_select("ul") do - assert_select("li a.test-dynamic-child-0", "Test dynamic child 0") - assert_select("li a.test-dynamic-child-1", "Test dynamic child 1") - assert_select("li a.test-dynamic-child-2", "Test dynamic child 2") - assert_select("li a.test-dynamic-child-3", "Test dynamic child 3") - assert_select("li a.test-dynamic-child-4", "Test dynamic child 4") - assert_select("li a.test-dynamic-child-5", "Test dynamic child 5") - end - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_children_without_an_array - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})} - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @output_buffer = render_menu_node(parent_node, Project.find(1)) - end - end - - def test_render_menu_node_with_incorrect_children - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| ["a string"] } - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @output_buffer = render_menu_node(parent_node, Project.find(1)) - end - - end - - def test_menu_items_for_should_yield_all_items_if_passed_a_block - menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items_yielded = [] - menu_items_for(menu_name) do |item| - items_yielded << item - end - - assert_equal 3, items_yielded.size - end - - def test_menu_items_for_should_return_all_items - menu_name = :test_menu_items_for_should_return_all_items - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items = menu_items_for(menu_name) - assert_equal 3, items.size - end - - def test_menu_items_for_should_skip_unallowed_items_on_a_project - menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { }) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 2, items.size - end - - def test_menu_items_for_should_skip_items_that_fail_the_conditions - menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, - {:controller => 'issues', :action => 'index' }, - { :if => Proc.new { false }}) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 1, items.size - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/08/088eae42f08379f3106075b388f036d1754d148c.svn-base --- a/.svn/pristine/08/088eae42f08379f3106075b388f036d1754d148c.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module EnumerationsHelper -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/08/08c1f1410c30c4d18ef6cbcbd8954d1721fb044b.svn-base --- a/.svn/pristine/08/08c1f1410c30c4d18ef6cbcbd8954d1721fb044b.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +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 WatchersControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, - :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers - - def setup - User.current = nil - end - - def test_watch_a_single_object - @request.session[:user_id] = 3 - assert_difference('Watcher.count') do - xhr :post, :watch, :object_type => 'issue', :object_id => '1' - assert_response :success - assert_include '$(".issue-1-watcher")', response.body - end - assert Issue.find(1).watched_by?(User.find(3)) - end - - def test_watch_a_collection_with_a_single_object - @request.session[:user_id] = 3 - assert_difference('Watcher.count') do - xhr :post, :watch, :object_type => 'issue', :object_id => ['1'] - assert_response :success - assert_include '$(".issue-1-watcher")', response.body - end - assert Issue.find(1).watched_by?(User.find(3)) - end - - def test_watch_a_collection_with_multiple_objects - @request.session[:user_id] = 3 - assert_difference('Watcher.count', 2) do - xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3'] - assert_response :success - assert_include '$(".issue-bulk-watcher")', response.body - end - assert Issue.find(1).watched_by?(User.find(3)) - assert Issue.find(3).watched_by?(User.find(3)) - end - - def test_watch_should_be_denied_without_permission - Role.find(2).remove_permission! :view_issues - @request.session[:user_id] = 3 - assert_no_difference('Watcher.count') do - xhr :post, :watch, :object_type => 'issue', :object_id => '1' - assert_response 403 - end - end - - def test_watch_invalid_class_should_respond_with_404 - @request.session[:user_id] = 3 - assert_no_difference('Watcher.count') do - xhr :post, :watch, :object_type => 'foo', :object_id => '1' - assert_response 404 - end - end - - def test_watch_invalid_object_should_respond_with_404 - @request.session[:user_id] = 3 - assert_no_difference('Watcher.count') do - xhr :post, :watch, :object_type => 'issue', :object_id => '999' - assert_response 404 - end - end - - def test_unwatch - @request.session[:user_id] = 3 - assert_difference('Watcher.count', -1) do - xhr :delete, :unwatch, :object_type => 'issue', :object_id => '2' - assert_response :success - assert_include '$(".issue-2-watcher")', response.body - end - assert !Issue.find(1).watched_by?(User.find(3)) - end - - def test_unwatch_a_collection_with_multiple_objects - @request.session[:user_id] = 3 - Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) - Watcher.create!(:user_id => 3, :watchable => Issue.find(3)) - - assert_difference('Watcher.count', -2) do - xhr :delete, :unwatch, :object_type => 'issue', :object_id => ['1', '3'] - assert_response :success - assert_include '$(".issue-bulk-watcher")', response.body - end - assert !Issue.find(1).watched_by?(User.find(3)) - assert !Issue.find(3).watched_by?(User.find(3)) - end - - def test_new - @request.session[:user_id] = 2 - xhr :get, :new, :object_type => 'issue', :object_id => '2' - assert_response :success - assert_match /ajax-modal/, response.body - end - - def test_new_for_new_record_with_project_id - @request.session[:user_id] = 2 - xhr :get, :new, :project_id => 1 - assert_response :success - assert_equal Project.find(1), assigns(:project) - assert_match /ajax-modal/, response.body - end - - def test_new_for_new_record_with_project_identifier - @request.session[:user_id] = 2 - xhr :get, :new, :project_id => 'ecookbook' - assert_response :success - assert_equal Project.find(1), assigns(:project) - assert_match /ajax-modal/, response.body - end - - def test_create - @request.session[:user_id] = 2 - assert_difference('Watcher.count') do - xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'} - assert_response :success - assert_match /watchers/, response.body - assert_match /ajax-modal/, response.body - end - assert Issue.find(2).watched_by?(User.find(4)) - end - - def test_create_multiple - @request.session[:user_id] = 2 - assert_difference('Watcher.count', 2) do - xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_ids => ['4', '7']} - assert_response :success - assert_match /watchers/, response.body - assert_match /ajax-modal/, response.body - end - assert Issue.find(2).watched_by?(User.find(4)) - assert Issue.find(2).watched_by?(User.find(7)) - end - - def test_autocomplete_on_watchable_creation - @request.session[:user_id] = 2 - xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook' - assert_response :success - assert_select 'input', :count => 4 - assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' - assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' - assert_select 'input[name=?][value=8]', 'watcher[user_ids][]' - assert_select 'input[name=?][value=9]', 'watcher[user_ids][]' - end - - def test_autocomplete_on_watchable_update - @request.session[:user_id] = 2 - xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook' - assert_response :success - assert_select 'input', :count => 3 - assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' - assert_select 'input[name=?][value=8]', 'watcher[user_ids][]' - assert_select 'input[name=?][value=9]', 'watcher[user_ids][]' - - end - - def test_append - @request.session[:user_id] = 2 - assert_no_difference 'Watcher.count' do - xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook' - assert_response :success - assert_include 'watchers_inputs', response.body - assert_include 'issue[watcher_user_ids][]', response.body - end - end - - def test_remove_watcher - @request.session[:user_id] = 2 - assert_difference('Watcher.count', -1) do - xhr :delete, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' - assert_response :success - assert_match /watchers/, response.body - end - assert !Issue.find(2).watched_by?(User.find(3)) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/09/094f0fabb02fa5ce798642abc8745a5aba89d2d9.svn-base --- a/.svn/pristine/09/094f0fabb02fa5ce798642abc8745a5aba89d2d9.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/09/09d005add00d07aeb449a6c04ad00db8e1f72fbd.svn-base --- a/.svn/pristine/09/09d005add00d07aeb449a6c04ad00db8e1f72fbd.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/0a/0a01ef88ba34bfc6a3c1cef8891538f7504e9b0c.svn-base --- a/.svn/pristine/0a/0a01ef88ba34bfc6a3c1cef8891538f7504e9b0c.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +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 I18n - def self.included(base) - base.extend Redmine::I18n - end - - def l(*args) - case args.size - when 1 - ::I18n.t(*args) - when 2 - if args.last.is_a?(Hash) - ::I18n.t(*args) - elsif args.last.is_a?(String) - ::I18n.t(args.first, :value => args.last) - else - ::I18n.t(args.first, :count => args.last) - end - else - raise "Translation string with multiple values: #{args.first}" - end - end - - def l_or_humanize(s, options={}) - k = "#{options[:prefix]}#{s}".to_sym - ::I18n.t(k, :default => s.to_s.humanize) - end - - def l_hours(hours) - hours = hours.to_f - l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => ("%.2f" % hours.to_f)) - end - - def ll(lang, str, value=nil) - ::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" }) - end - - def format_date(date) - return nil unless date - options = {} - options[:format] = Setting.date_format unless Setting.date_format.blank? - options[:locale] = User.current.language unless User.current.language.blank? - ::I18n.l(date.to_date, options) - end - - def format_time(time, include_date = true) - return nil unless time - options = {} - options[:format] = (Setting.time_format.blank? ? :time : Setting.time_format) - options[:locale] = User.current.language unless User.current.language.blank? - time = time.to_time if time.is_a?(String) - zone = User.current.time_zone - local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time) - (include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, options) - end - - def day_name(day) - ::I18n.t('date.day_names')[day % 7] - end - - def day_letter(day) - ::I18n.t('date.abbr_day_names')[day % 7].first - end - - def month_name(month) - ::I18n.t('date.month_names')[month] - end - - def valid_languages - ::I18n.available_locales - end - - # Returns an array of languages names and code sorted by names, example: - # [["Deutsch", "de"], ["English", "en"] ...] - # - # The result is cached to prevent from loading all translations files. - def languages_options - ActionController::Base.cache_store.fetch "i18n/languages_options" do - valid_languages.map {|lang| [ll(lang.to_s, :general_lang_name), lang.to_s]}.sort {|x,y| x.first <=> y.first } - end - end - - def find_language(lang) - @@languages_lookup = valid_languages.inject({}) {|k, v| k[v.to_s.downcase] = v; k } - @@languages_lookup[lang.to_s.downcase] - end - - def set_language_if_valid(lang) - if l = find_language(lang) - ::I18n.locale = l - end - end - - def current_language - ::I18n.locale - end - - # Custom backend based on I18n::Backend::Simple with the following changes: - # * lazy loading of translation files - # * available_locales are determined by looking at translation file names - class Backend - (class << self; self; end).class_eval { public :include } - - module Implementation - include ::I18n::Backend::Base - - # Stores translations for the given locale in memory. - # This uses a deep merge for the translations hash, so existing - # translations will be overwritten by new ones only at the deepest - # level of the hash. - def store_translations(locale, data, options = {}) - locale = locale.to_sym - translations[locale] ||= {} - data = data.deep_symbolize_keys - translations[locale].deep_merge!(data) - end - - # Get available locales from the translations filenames - def available_locales - @available_locales ||= ::I18n.load_path.map {|path| File.basename(path, '.*')}.uniq.sort.map(&:to_sym) - end - - # Clean up translations - def reload! - @translations = nil - @available_locales = nil - super - end - - protected - - def init_translations(locale) - locale = locale.to_s - paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale} - load_translations(paths) - translations[locale] ||= {} - end - - def translations - @translations ||= {} - end - - # Looks up a translation from the translations hash. Returns nil if - # eiher key is nil, or locale, scope or key do not exist as a key in the - # nested translations hash. Splits keys or scopes containing dots - # into multiple keys, i.e. currency.format is regarded the same as - # %w(currency format). - def lookup(locale, key, scope = [], options = {}) - init_translations(locale) unless translations.key?(locale) - keys = ::I18n.normalize_keys(locale, key, scope, options[:separator]) - - keys.inject(translations) do |result, _key| - _key = _key.to_sym - return nil unless result.is_a?(Hash) && result.has_key?(_key) - result = result[_key] - result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) - result - end - end - end - - include Implementation - # Adds fallback to default locale for untranslated strings - include ::I18n::Backend::Fallbacks - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/0b/0b7befce1d7322f245834ffc9480adf8d5c60890.svn-base --- a/.svn/pristine/0b/0b7befce1d7322f245834ffc9480adf8d5c60890.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,294 +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_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 e248c7af89ec -r b450a9d58aed .svn/pristine/0b/0b8a142f46ed608799d7faf6016772f4b1f09f42.svn-base --- a/.svn/pristine/0b/0b8a142f46ed608799d7faf6016772f4b1f09f42.svn-base Mon Mar 17 08:54:02 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 RoutingAdminTest < ActionController::IntegrationTest - def test_administration_panel - assert_routing( - { :method => 'get', :path => "/admin" }, - { :controller => 'admin', :action => 'index' } - ) - assert_routing( - { :method => 'get', :path => "/admin/projects" }, - { :controller => 'admin', :action => 'projects' } - ) - assert_routing( - { :method => 'get', :path => "/admin/plugins" }, - { :controller => 'admin', :action => 'plugins' } - ) - assert_routing( - { :method => 'get', :path => "/admin/info" }, - { :controller => 'admin', :action => 'info' } - ) - assert_routing( - { :method => 'get', :path => "/admin/test_email" }, - { :controller => 'admin', :action => 'test_email' } - ) - assert_routing( - { :method => 'post', :path => "/admin/default_configuration" }, - { :controller => 'admin', :action => 'default_configuration' } - ) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/0b/0bba8bba58df7fc70a5a93a0d2973b34736d639d.svn-base --- a/.svn/pristine/0b/0bba8bba58df7fc70a5a93a0d2973b34736d639d.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,448 +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' -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 e248c7af89ec -r b450a9d58aed .svn/pristine/0b/0bd8b54775e82ba1bfa004993bdb8bf956e85c99.svn-base --- a/.svn/pristine/0b/0bd8b54775e82ba1bfa004993bdb8bf956e85c99.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +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) - 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.visible.all.map {|cf| QueryCustomFieldColumn.new(cf) } - @available_columns += IssueCustomField.visible.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(','))). - includes(:activity) - end - - def sql_for_activity_id_field(field, operator, value) - condition_on_id = sql_for_field(field, operator, value, Enumeration.table_name, 'id') - condition_on_parent_id = sql_for_field(field, operator, value, Enumeration.table_name, 'parent_id') - ids = value.map(&:to_i).join(',') - table_name = Enumeration.table_name - if operator == '=' - "(#{table_name}.id IN (#{ids}) OR #{table_name}.parent_id IN (#{ids}))" - else - "(#{table_name}.id NOT IN (#{ids}) AND (#{table_name}.parent_id IS NULL OR #{table_name}.parent_id NOT IN (#{ids})))" - end - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/0c/0ca7bda81025c5f7a9ebad14d8b0c08f3a0b8176.svn-base --- a/.svn/pristine/0c/0ca7bda81025c5f7a9ebad14d8b0c08f3a0b8176.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/0c/0cb5ab49f6fd89443044a7c2f37992f0e2874866.svn-base --- a/.svn/pristine/0c/0cb5ab49f6fd89443044a7c2f37992f0e2874866.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,257 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/git_adapter' - -class Repository::Git < Repository - attr_protected :root_url - validates_presence_of :url - - def self.human_attribute_name(attribute_key_name, *args) - attr_name = attribute_key_name.to_s - if attr_name == "url" - attr_name = "path_to_repository" - end - super(attr_name, *args) - end - - def self.scm_adapter_class - Redmine::Scm::Adapters::GitAdapter - end - - def self.scm_name - 'Git' - end - - def report_last_commit - extra_report_last_commit - end - - def extra_report_last_commit - return false if extra_info.nil? - v = extra_info["extra_report_last_commit"] - return false if v.nil? - v.to_s != '0' - end - - def supports_directory_revisions? - true - end - - def supports_revision_graph? - true - end - - def repo_log_encoding - 'UTF-8' - end - - # Returns the identifier for the given git changeset - def self.changeset_identifier(changeset) - changeset.scmid - end - - # Returns the readable identifier for the given git changeset - def self.format_changeset_identifier(changeset) - changeset.revision[0, 8] - end - - def branches - scm.branches - end - - def tags - scm.tags - end - - def default_branch - scm.default_branch - rescue Exception => e - logger.error "git: error during get default branch: #{e.message}" - nil - end - - def find_changeset_by_name(name) - if name.present? - changesets.where(:revision => name.to_s).first || - changesets.where('scmid LIKE ?', "#{name}%").first - end - end - - def entries(path=nil, identifier=nil) - entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit) - load_entries_changesets(entries) - entries - end - - # With SCMs that have a sequential commit numbering, - # such as Subversion and Mercurial, - # Redmine is able to be clever and only fetch changesets - # going forward from the most recent one it knows about. - # - # However, Git does not have a sequential commit numbering. - # - # In order to fetch only new adding revisions, - # Redmine needs to save "heads". - # - # In Git and Mercurial, revisions are not in date order. - # Redmine Mercurial fixed issues. - # * Redmine Takes Too Long On Large Mercurial Repository - # http://www.redmine.org/issues/3449 - # * Sorting for changesets might go wrong on Mercurial repos - # http://www.redmine.org/issues/3567 - # - # Database revision column is text, so Redmine can not sort by revision. - # Mercurial has revision number, and revision number guarantees revision order. - # Redmine Mercurial model stored revisions ordered by database id to database. - # So, Redmine Mercurial model can use correct ordering revisions. - # - # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" - # to get limited revisions from old to new. - # But, Git 1.7.3.4 does not support --reverse with -n or --skip. - # - # The repository can still be fully reloaded by calling #clear_changesets - # before fetching changesets (eg. for offline resync) - def fetch_changesets - scm_brs = branches - return if scm_brs.nil? || scm_brs.empty? - - h1 = extra_info || {} - h = h1.dup - repo_heads = scm_brs.map{ |br| br.scmid } - h["heads"] ||= [] - prev_db_heads = h["heads"].dup - if prev_db_heads.empty? - prev_db_heads += heads_from_branches_hash - end - return if prev_db_heads.sort == repo_heads.sort - - h["db_consistent"] ||= {} - if changesets.count == 0 - h["db_consistent"]["ordering"] = 1 - merge_extra_info(h) - self.save - elsif ! h["db_consistent"].has_key?("ordering") - h["db_consistent"]["ordering"] = 0 - merge_extra_info(h) - self.save - end - save_revisions(prev_db_heads, repo_heads) - end - - def save_revisions(prev_db_heads, repo_heads) - h = {} - opts = {} - opts[:reverse] = true - opts[:excludes] = prev_db_heads - opts[:includes] = repo_heads - - revisions = scm.revisions('', nil, nil, opts) - return if revisions.blank? - - # Make the search for existing revisions in the database in a more sufficient manner - # - # Git branch is the reference to the specific revision. - # Git can *delete* remote branch and *re-push* branch. - # - # $ git push remote :branch - # $ git push remote branch - # - # After deleting branch, revisions remain in repository until "git gc". - # On git 1.7.2.3, default pruning date is 2 weeks. - # So, "git log --not deleted_branch_head_revision" return code is 0. - # - # After re-pushing branch, "git log" returns revisions which are saved in database. - # So, Redmine needs to scan revisions and database every time. - # - # This is replacing the one-after-one queries. - # Find all revisions, that are in the database, and then remove them from the revision array. - # Then later we won't need any conditions for db existence. - # Query for several revisions at once, and remove them from the revisions array, if they are there. - # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits). - # If there are no revisions (because the original code's algorithm filtered them), - # then this part will be stepped over. - # We make queries, just if there is any revision. - limit = 100 - offset = 0 - revisions_copy = revisions.clone # revisions will change - while offset < revisions_copy.size - scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid} - recent_changesets_slice = changesets.where(:scmid => scmids).all - # 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.where(:scmid => revisions.map {|c| c.scmid}).all - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/0c/0cc9601ec1a6e3a27ab4a946d394cf4ba82254b4.svn-base --- a/.svn/pristine/0c/0cc9601ec1a6e3a27ab4a946d394cf4ba82254b4.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +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 TimeEntryActivityCustomField < CustomField - def type_name - :enumeration_activities - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/0e/0e9c8f11b3db41702079f085c94dfa01133abcd9.svn-base --- a/.svn/pristine/0e/0e9c8f11b3db41702079f085c94dfa01133abcd9.svn-base Mon Mar 17 08:54:02 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 File.expand_path('../../../../../test_helper', __FILE__) - -class PdfTest < ActiveSupport::TestCase - fixtures :users, :projects, :roles, :members, :member_roles, - :enabled_modules, :issues, :trackers, :attachments - - def test_fix_text_encoding_nil - assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8") - assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1") - end - - def test_rdm_pdf_iconv_cannot_convert_ja_cp932 - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b" - utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" - utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" - if utf8_txt_1.respond_to?(:force_encoding) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - assert_equal "?\x91\xd4".force_encoding("ASCII-8BIT"), txt_1 - assert_equal "?\x91\xd4?".force_encoding("ASCII-8BIT"), txt_2 - assert_equal "??\x91\xd4?".force_encoding("ASCII-8BIT"), txt_3 - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - assert_equal "ASCII-8BIT", txt_3.encoding.to_s - elsif RUBY_PLATFORM == 'java' - assert_equal "??", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - assert_equal "???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - assert_equal "????", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - else - assert_equal "???\x91\xd4", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - assert_equal "???\x91\xd4???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - assert_equal "??????\x91\xd4???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - end - end - - def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en - str1 = "Texte encod\xe9 en ISO-8859-1" - str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" - str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8') - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8') - if txt_1.respond_to?(:force_encoding) - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1", txt_1 - assert_equal "?a?b?c?d?e test", txt_2 - end - - def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja - str1 = "Texte encod\xe9 en ISO-8859-1" - str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" - str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding) - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding) - if txt_1.respond_to?(:force_encoding) - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1", txt_1 - assert_equal "?a?b?c?d?e test", txt_2 - end - - def test_attach - set_fixtures_attachments_directory - - str2 = "\x83e\x83X\x83g" - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - - a1 = Attachment.find(17) - a2 = Attachment.find(19) - - User.current = User.find(1) - assert a1.readable? - assert a1.visible? - assert a2.readable? - assert a2.visible? - - aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") - assert_not_nil aa1 - assert_equal 17, aa1.id - aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) - assert_not_nil aa2 - assert_equal 19, aa2.id - - User.current = nil - assert a1.readable? - assert (! a1.visible?) - assert a2.readable? - assert (! a2.visible?) - - aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") - assert_equal nil, aa1 - aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) - assert_equal nil, aa2 - - set_tmp_attachments_directory - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/0f/0fc81c2978046c22ad1fddeadeabe4e288474193.svn-base --- a/.svn/pristine/0f/0fc81c2978046c22ad1fddeadeabe4e288474193.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +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 RepositoriesBazaarControllerTest < ActionController::TestCase - tests RepositoriesController - - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s - REPOSITORY_PATH_TRUNK = File.join(REPOSITORY_PATH, "trunk") - PRJ_ID = 3 - CHAR_1_UTF8_HEX = "\xc3\x9c" - - def setup - User.current = nil - @project = Project.find(PRJ_ID) - @repository = Repository::Bazaar.create( - :project => @project, - :url => REPOSITORY_PATH_TRUNK, - :log_encoding => 'UTF-8') - assert @repository - @char_1_utf8 = CHAR_1_UTF8_HEX.dup - if @char_1_utf8.respond_to?(:force_encoding) - @char_1_utf8.force_encoding('UTF-8') - end - 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 => 'Bazaar' - assert_response :success - assert_template 'new' - assert_kind_of Repository::Bazaar, assigns(:repository) - assert assigns(:repository).new_record? - end - - def test_browse_root - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 2, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'} - end - - def test_browse_directory - get :show, :id => PRJ_ID, :path => repository_path_hash(['directory'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'directory/edit.png', entry.path - end - - def test_browse_at_given_revision - get :show, :id => PRJ_ID, :path => repository_path_hash([])[:param], - :rev => 3 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], - assigns(:entries).collect(&:name) - end - - def test_changes - get :changes, :id => PRJ_ID, - :path => repository_path_hash(['doc-mkdir.txt'])[:param] - assert_response :success - assert_template 'changes' - assert_tag :tag => 'h2', :content => 'doc-mkdir.txt' - end - - def test_entry_show - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['directory', 'doc-ls.txt'])[:param] - assert_response :success - assert_template 'entry' - # Line 19 - assert_tag :tag => 'th', - :content => /29/, - :attributes => { :class => /line-num/ }, - :sibling => { :tag => 'td', :content => /Show help message/ } - end - - def test_entry_download - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['directory', 'doc-ls.txt'])[:param], - :format => 'raw' - assert_response :success - # File content - assert @response.body.include?('Show help message') - end - - def test_directory_entry - get :entry, :id => PRJ_ID, - :path => repository_path_hash(['directory'])[:param] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'directory', assigns(:entry).name - end - - def test_diff - # Full diff of changeset 3 - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 3, :type => dt - assert_response :success - assert_template 'diff' - # Line 11 removed - assert_tag :tag => 'th', - :content => '11', - :sibling => { :tag => 'td', - :attributes => { :class => /diff_out/ }, - :content => /Display more information/ } - end - end - - def test_annotate - get :annotate, :id => PRJ_ID, - :path => repository_path_hash(['doc-mkdir.txt'])[:param] - assert_response :success - assert_template 'annotate' - assert_select "th.line-num", :text => '2' do - assert_select "+ td.revision" do - assert_select "a", :text => '3' - assert_select "+ td.author", :text => "jsmith@" do - assert_select "+ td", - :text => "Main purpose:" - end - end - end - end - - def test_annotate_author_escaping - repository = Repository::Bazaar.create( - :project => @project, - :url => File.join(REPOSITORY_PATH, "author_escaping"), - :identifier => 'author_escaping', - :log_encoding => 'UTF-8') - assert repository - get :annotate, :id => PRJ_ID, :repository_id => 'author_escaping', - :path => repository_path_hash(['author-escaping-test.txt'])[:param] - assert_response :success - assert_template 'annotate' - assert_select "th.line-num", :text => '1' do - assert_select "+ td.revision" do - assert_select "a", :text => '2' - assert_select "+ td.author", :text => "test &" do - assert_select "+ td", - :text => "author escaping test" - end - end - end - end - - if REPOSITORY_PATH.respond_to?(:force_encoding) - def test_annotate_author_non_ascii - log_encoding = nil - if Encoding.locale_charmap == "UTF-8" || - Encoding.locale_charmap == "ISO-8859-1" - log_encoding = Encoding.locale_charmap - end - unless log_encoding.nil? - repository = Repository::Bazaar.create( - :project => @project, - :url => File.join(REPOSITORY_PATH, "author_non_ascii"), - :identifier => 'author_non_ascii', - :log_encoding => log_encoding) - assert repository - get :annotate, :id => PRJ_ID, :repository_id => 'author_non_ascii', - :path => repository_path_hash(['author-non-ascii-test.txt'])[:param] - assert_response :success - assert_template 'annotate' - assert_select "th.line-num", :text => '1' do - assert_select "+ td.revision" do - assert_select "a", :text => '2' - assert_select "+ td.author", :text => "test #{@char_1_utf8}" do - assert_select "+ td", - :text => "author non ASCII test" - end - end - end - end - end - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - assert @repository.changesets.count > 0 - - 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::Bazaar.create!( - :project => @project, - :url => "/invalid", - :log_encoding => 'UTF-8') - @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 - else - puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/11/11276ad92ab446760ef27dc63e4ad98666e8240c.svn-base --- a/.svn/pristine/11/11276ad92ab446760ef27dc63e4ad98666e8240c.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +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 RoutingAccountTest < ActionController::IntegrationTest - def test_account - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/login" }, - { :controller => 'account', :action => 'login' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/logout" }, - { :controller => 'account', :action => 'logout' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/account/register" }, - { :controller => 'account', :action => 'register' } - ) - end - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/account/lost_password" }, - { :controller => 'account', :action => 'lost_password' } - ) - end - assert_routing( - { :method => 'get', :path => "/account/activate" }, - { :controller => 'account', :action => 'activate' } - ) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/11/11c16631f55588a98678a93f0bb375404c2e534c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/11/11c16631f55588a98678a93f0bb375404c2e534c.svn-base Tue Sep 09 09:28:31 2014 +0100 @@ -0,0 +1,33 @@ +# Settings specified here will take precedence over those in config/application.rb +RedmineApp::Application.configure do + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + ##### + # Customize the default logger + # http://www.ruby-doc.org/stdlib-1.8.7/libdoc/logger/rdoc/Logger.html + # + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + # + # Rotate logs bigger than 1MB, keeps no more than 7 rotated logs around. + # When setting a new Logger, make sure to set it's log level too. + # + # config.logger = Logger.new(config.log_path, 7, 1048576) + # config.logger.level = Logger::INFO + + # Full error reports are disabled and caching is turned on + config.action_controller.perform_caching = true + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors if you bad email addresses should just be ignored + config.action_mailer.raise_delivery_errors = false + + # No email in production log + config.action_mailer.logger = nil + + config.active_support.deprecation = :log +end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/12/120840d537ad68f417065b102e2763e8f985fe62.svn-base --- a/.svn/pristine/12/120840d537ad68f417065b102e2763e8f985fe62.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +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 MembersController < ApplicationController - model_object Member - before_filter :find_model_object, :except => [:index, :create, :autocomplete] - before_filter :find_project_from_association, :except => [:index, :create, :autocomplete] - before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete] - before_filter :authorize - accept_api_auth :index, :show, :create, :update, :destroy - - def index - @offset, @limit = api_offset_and_limit - @member_count = @project.member_principals.count - @member_pages = Paginator.new @member_count, @limit, params['page'] - @offset ||= @member_pages.offset - @members = @project.member_principals.all( - :order => "#{Member.table_name}.id", - :limit => @limit, - :offset => @offset - ) - - respond_to do |format| - format.html { head 406 } - format.api - end - end - - def show - respond_to do |format| - format.html { head 406 } - format.api - end - end - - def create - members = [] - if params[:membership] - if params[:membership][:user_ids] - attrs = params[:membership].dup - user_ids = attrs.delete(:user_ids) - user_ids.each do |user_id| - members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id) - end - else - members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id]) - end - @project.members << members - end - - respond_to do |format| - format.html { redirect_to_settings_in_projects } - format.js { @members = members } - format.api { - @member = members.first - if @member.valid? - render :action => 'show', :status => :created, :location => membership_url(@member) - else - render_validation_errors(@member) - end - } - end - end - - def update - if params[:membership] - @member.role_ids = params[:membership][:role_ids] - end - saved = @member.save - respond_to do |format| - format.html { redirect_to_settings_in_projects } - format.js - format.api { - if saved - render_api_ok - else - render_validation_errors(@member) - end - } - end - end - - def destroy - if request.delete? && @member.deletable? - @member.destroy - end - respond_to do |format| - format.html { redirect_to_settings_in_projects } - format.js - format.api { - if @member.destroyed? - render_api_ok - else - head :unprocessable_entity - end - } - end - end - - def autocomplete - respond_to do |format| - format.js - end - end - - private - - def redirect_to_settings_in_projects - redirect_to settings_project_path(@project, :tab => 'members') - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/13/134ab6c377c7865f7eab83c298719a41c97ec009.svn-base --- a/.svn/pristine/13/134ab6c377c7865f7eab83c298719a41c97ec009.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/13/1398acbd60647babc95a74c09ce2afa2acef1b0b.svn-base --- a/.svn/pristine/13/1398acbd60647babc95a74c09ce2afa2acef1b0b.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +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::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 e248c7af89ec -r b450a9d58aed .svn/pristine/13/13e6190365f9e0ad8532e5e563cf7ad5676797a5.svn-base --- a/.svn/pristine/13/13e6190365f9e0ad8532e5e563cf7ad5676797a5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1044 +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 :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.builtin_role - 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 - - def principals - @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq - end - - def users - @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq - 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) - @principals = nil - @users = nil - @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. - includes(:project). - where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED) - end - - # Returns a scope of the Versions used by the project - def shared_versions - if new_record? - Version. - includes(:project). - where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED) - else - @shared_versions ||= begin - r = root? ? self : root - Version. - includes(:project). - where("#{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 a scope 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. - sorted. - where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" + - " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" + - " WHERE cfp.project_id = ?)", true, id) - 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.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(: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 e248c7af89ec -r b450a9d58aed .svn/pristine/13/13f1c7e824023a07ff2178b5bb3f5f1ee4de792e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/13/13f1c7e824023a07ff2178b5bb3f5f1ee4de792e.svn-base Tue Sep 09 09:28:31 2014 +0100 @@ -0,0 +1,25 @@ + + +Redmine 500 error + + +

Internal error

+

An error occurred on the page you were trying to access.
+ If you continue to experience problems please contact your Redmine administrator for assistance.

+

If you are the Redmine administrator, check your log files for details about the error.

+

Back

+ + diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/13/13f3f51390f5b007f8a6b4667437b3436606259a.svn-base --- a/.svn/pristine/13/13f3f51390f5b007f8a6b4667437b3436606259a.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +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 ActivityProvider - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_activity_provider(options = {}) - unless self.included_modules.include?(Redmine::Acts::ActivityProvider::InstanceMethods) - cattr_accessor :activity_provider_options - send :include, Redmine::Acts::ActivityProvider::InstanceMethods - end - - options.assert_valid_keys(:type, :permission, :timestamp, :author_key, :find_options) - self.activity_provider_options ||= {} - - # One model can provide different event types - # We store these options in activity_provider_options hash - event_type = options.delete(:type) || self.name.underscore.pluralize - - options[:timestamp] ||= "#{table_name}.created_on" - options[:find_options] ||= {} - options[:author_key] = "#{table_name}.#{options[:author_key]}" if options[:author_key].is_a?(Symbol) - self.activity_provider_options[event_type] = options - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Returns events of type event_type visible by user that occured between from and to - def find_events(event_type, user, from, to, options) - provider_options = activity_provider_options[event_type] - raise "#{self.name} can not provide #{event_type} events." if provider_options.nil? - - scope = self - - if from && to - scope = scope.where("#{provider_options[:timestamp]} BETWEEN ? AND ?", from, to) - end - - if options[:author] - return [] if provider_options[:author_key].nil? - scope = scope.where("#{provider_options[:author_key]} = ?", options[:author].id) - end - - if options[:limit] - # id and creation time should be in same order in most cases - scope = scope.reorder("#{table_name}.id DESC").limit(options[:limit]) - end - - if provider_options.has_key?(:permission) - scope = scope.where(Project.allowed_to_condition(user, provider_options[:permission] || :view_project, options)) - elsif respond_to?(:visible) - scope = scope.visible(user, options) - else - ActiveSupport::Deprecation.warn "acts_as_activity_provider with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." - scope = scope.where(Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options)) - end - - scope.all(provider_options[:find_options].dup) - end - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/15/1501976e33da8b363bb737aa1d51d7b528152400.svn-base --- a/.svn/pristine/15/1501976e33da8b363bb737aa1d51d7b528152400.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,262 +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::I18nTest < ActiveSupport::TestCase - include Redmine::I18n - include ActionView::Helpers::NumberHelper - - def setup - User.current.language = nil - end - - def teardown - set_language_if_valid 'en' - end - - def test_date_format_default - set_language_if_valid 'en' - today = Date.today - Setting.date_format = '' - assert_equal I18n.l(today), format_date(today) - end - - def test_date_format - set_language_if_valid 'en' - today = Date.today - Setting.date_format = '%d %m %Y' - assert_equal today.strftime('%d %m %Y'), format_date(today) - end - - def test_date_format_default_with_user_locale - set_language_if_valid 'es' - today = now = Time.parse('2011-02-20 14:00:00') - Setting.date_format = '%d %B %Y' - User.current.language = 'fr' - s1 = "20 f\xc3\xa9vrier 2011" - s1.force_encoding("UTF-8") if s1.respond_to?(:force_encoding) - assert_equal s1, format_date(today) - User.current.language = nil - assert_equal '20 Febrero 2011', format_date(today) - end - - def test_date_and_time_for_each_language - Setting.date_format = '' - valid_languages.each do |lang| - set_language_if_valid lang - assert_nothing_raised "#{lang} failure" do - format_date(Date.today) - format_time(Time.now) - format_time(Time.now, false) - assert_not_equal 'default', ::I18n.l(Date.today, :format => :default), - "date.formats.default missing in #{lang}" - assert_not_equal 'time', ::I18n.l(Time.now, :format => :time), - "time.formats.time missing in #{lang}" - end - assert l('date.day_names').is_a?(Array) - assert_equal 7, l('date.day_names').size - - assert l('date.month_names').is_a?(Array) - assert_equal 13, l('date.month_names').size - end - end - - def test_time_for_each_zone - ActiveSupport::TimeZone.all.each do |zone| - User.current.stubs(:time_zone).returns(zone.name) - assert_nothing_raised "#{zone} failure" do - format_time(Time.now) - end - end - end - - def test_time_format - set_language_if_valid 'en' - now = Time.parse('2011-02-20 15:45:22') - with_settings :time_format => '%H:%M' do - with_settings :date_format => '' do - assert_equal '02/20/2011 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - end - end - - def test_time_format_default - set_language_if_valid 'en' - now = Time.parse('2011-02-20 15:45:22') - with_settings :time_format => '' do - with_settings :date_format => '' do - assert_equal '02/20/2011 03:45 PM', format_time(now) - assert_equal '03:45 PM', format_time(now, false) - end - with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 03:45 PM', format_time(now) - assert_equal '03:45 PM', format_time(now, false) - end - end - end - - def test_time_format_default_with_user_locale - set_language_if_valid 'en' - User.current.language = 'fr' - now = Time.parse('2011-02-20 15:45:22') - with_settings :time_format => '' do - with_settings :date_format => '' do - assert_equal '20/02/2011 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - end - end - - def test_utc_time_format - set_language_if_valid 'en' - now = Time.now - Setting.date_format = '%d %m %Y' - Setting.time_format = '%H %M' - assert_equal now.strftime('%d %m %Y %H %M'), format_time(now.utc) - assert_equal now.strftime('%H %M'), format_time(now.utc, false) - end - - def test_number_to_human_size_for_each_language - valid_languages.each do |lang| - set_language_if_valid lang - assert_nothing_raised "#{lang} failure" do - size = number_to_human_size(257024) - assert_match /251/, size - end - end - end - - def test_day_name - set_language_if_valid 'fr' - assert_equal 'dimanche', day_name(0) - assert_equal 'jeudi', day_name(4) - end - - def test_day_letter - set_language_if_valid 'fr' - assert_equal 'd', day_letter(0) - assert_equal 'j', day_letter(4) - end - - def test_number_to_currency_for_each_language - valid_languages.each do |lang| - set_language_if_valid lang - assert_nothing_raised "#{lang} failure" do - number_to_currency(-1000.2) - end - end - end - - def test_number_to_currency_default - set_language_if_valid 'bs' - assert_equal "KM -1000,20", number_to_currency(-1000.2) - set_language_if_valid 'de' - euro_sign = "\xe2\x82\xac" - euro_sign.force_encoding('UTF-8') if euro_sign.respond_to?(:force_encoding) - assert_equal "-1000,20 #{euro_sign}", number_to_currency(-1000.2) - end - - def test_valid_languages - assert valid_languages.is_a?(Array) - assert valid_languages.first.is_a?(Symbol) - end - - def test_languages_options - options = languages_options - assert options.is_a?(Array) - assert_equal valid_languages.size, options.size - assert_nil options.detect {|option| !option.is_a?(Array)} - assert_nil options.detect {|option| option.size != 2} - assert_nil options.detect {|option| !option.first.is_a?(String) || !option.last.is_a?(String)} - assert_include ["English", "en"], options - ja = "Japanese (\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e)" - ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) - assert_include [ja, "ja"], options - end - - def test_locales_validness - lang_files_count = Dir["#{Rails.root}/config/locales/*.yml"].size - assert_equal lang_files_count, valid_languages.size - valid_languages.each do |lang| - assert set_language_if_valid(lang) - end - set_language_if_valid('en') - end - - def test_valid_language - to_test = {'fr' => :fr, - 'Fr' => :fr, - 'zh' => :zh, - 'zh-tw' => :"zh-TW", - 'zh-TW' => :"zh-TW", - 'zh-ZZ' => nil } - to_test.each {|lang, expected| assert_equal expected, find_language(lang)} - end - - def test_fallback - ::I18n.backend.store_translations(:en, {:untranslated => "Untranslated string"}) - ::I18n.locale = 'en' - assert_equal "Untranslated string", l(:untranslated) - ::I18n.locale = 'fr' - assert_equal "Untranslated string", l(:untranslated) - - ::I18n.backend.store_translations(:fr, {:untranslated => "Pas de traduction"}) - ::I18n.locale = 'en' - assert_equal "Untranslated string", l(:untranslated) - ::I18n.locale = 'fr' - assert_equal "Pas de traduction", l(:untranslated) - end - - def test_utf8 - set_language_if_valid 'ja' - str_ja_yes = "\xe3\x81\xaf\xe3\x81\x84" - i18n_ja_yes = l(:general_text_Yes) - if str_ja_yes.respond_to?(:force_encoding) - str_ja_yes.force_encoding('UTF-8') - assert_equal "UTF-8", i18n_ja_yes.encoding.to_s - end - assert_equal str_ja_yes, i18n_ja_yes - end - - def test_traditional_chinese_locale - set_language_if_valid 'zh-TW' - str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" - if str_tw.respond_to?(:force_encoding) - str_tw.force_encoding('UTF-8') - end - assert_equal str_tw, l(:general_lang_name) - end - - def test_french_locale - set_language_if_valid 'fr' - str_fr = "Fran\xc3\xa7ais" - if str_fr.respond_to?(:force_encoding) - str_fr.force_encoding('UTF-8') - end - assert_equal str_fr, l(:general_lang_name) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/15/15b7222cb6b5695205873370eda995f4170a4d8e.svn-base --- a/.svn/pristine/15/15b7222cb6b5695205873370eda995f4170a4d8e.svn-base Mon Mar 17 08:54:02 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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/15/15c19f10a4e2450833d4d36fd99f66518ba44283.svn-base --- a/.svn/pristine/15/15c19f10a4e2450833d4d36fd99f66518ba44283.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +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::CustomFieldsTest < Redmine::ApiTest::Base - fixtures :users, :custom_fields - - def setup - Setting.rest_api_enabled = '1' - end - - test "GET /custom_fields.xml should return custom fields" do - get '/custom_fields.xml', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type - - assert_select 'custom_fields' do - assert_select 'custom_field' do - assert_select 'name', :text => 'Database' - assert_select 'id', :text => '2' - assert_select 'customized_type', :text => 'issue' - assert_select 'possible_values[type=array]' do - assert_select 'possible_value>value', :text => 'PostgreSQL' - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/15/15d709b3f6a05dc0aade44cd45030b0d995c1fdc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/15/15d709b3f6a05dc0aade44cd45030b0d995c1fdc.svn-base Tue Sep 09 09:28:31 2014 +0100 @@ -0,0 +1,23 @@ + + +Redmine 404 error + + +

Page not found

+

The page you were trying to access doesn't exist or has been removed.

+

Back

+ + diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/16/1611dde1fb0ec5f844a52d9ac947a7c023328e24.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/16/1611dde1fb0ec5f844a52d9ac947a7c023328e24.svn-base Tue Sep 09 09:28:31 2014 +0100 @@ -0,0 +1,38 @@ +# Redmine runs tests on own continuous integration server. +# http://www.redmine.org/projects/redmine/wiki/Continuous_integration +# You can also run tests on your environment. +language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0 + - jruby +matrix: + allow_failures: + # SCM tests fail randomly due to IO.popen(). + # https://github.com/jruby/jruby/issues/779 + - rvm: jruby +env: + - "TEST_SUITE=units DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=integration DATABASE_ADAPTER=postgresql" + - "TEST_SUITE=units DATABASE_ADAPTER=mysql" + - "TEST_SUITE=functionals DATABASE_ADAPTER=mysql" + - "TEST_SUITE=integration DATABASE_ADAPTER=mysql" + - "TEST_SUITE=units DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3" + - "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3" +before_install: + - "sudo apt-get update -qq" + - "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion" +script: + - "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem" + - "export SCMS" + - "git --version" + - "bundle install" + - "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml" + - "bundle install" + - "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci" +notifications: + email: false diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/16/163200e35f1d8c0484dc12a3ebb2256afa7733a8.svn-base --- a/.svn/pristine/16/163200e35f1d8c0484dc12a3ebb2256afa7733a8.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,437 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/17/17aa6f42b9915157fc191f136c4be06527af6f55.svn-base --- a/.svn/pristine/17/17aa6f42b9915157fc191f136c4be06527af6f55.svn-base Mon Mar 17 08:54:02 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. - -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 e248c7af89ec -r b450a9d58aed .svn/pristine/17/17eeb63ea895552f87458a07894df799f570d379.svn-base --- a/.svn/pristine/17/17eeb63ea895552f87458a07894df799f570d379.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 e248c7af89ec -r b450a9d58aed .svn/pristine/18/182957517dcdfdb9bed4a7cc03f63aeef4b1c15d.svn-base --- a/.svn/pristine/18/182957517dcdfdb9bed4a7cc03f63aeef4b1c15d.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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 UserPreference < ActiveRecord::Base - belongs_to :user - serialize :others - - attr_protected :others, :user_id - - before_save :set_others_hash - - def initialize(attributes=nil, *args) - super - self.others ||= {} - end - - def set_others_hash - self.others ||= {} - end - - def [](attr_name) - if has_attribute? attr_name - super - else - others ? others[attr_name] : nil - end - end - - def []=(attr_name, value) - if has_attribute? attr_name - super - else - h = (read_attribute(:others) || {}).dup - h.update(attr_name => value) - write_attribute(:others, h) - value - end - end - - def comments_sorting; self[:comments_sorting] end - def comments_sorting=(order); self[:comments_sorting]=order end - - def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end - def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end - - def no_self_notified; (self[:no_self_notified] == true || self[:no_self_notified] == '1'); end - def no_self_notified=(value); self[:no_self_notified]=value; end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/18/183dca49dc870131da0c794df1c3f5f569d0e6e5.svn-base --- a/.svn/pristine/18/183dca49dc870131da0c794df1c3f5f569d0e6e5.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module IssueStatusesHelper -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/18/18aaa55b2d77150962ade434e4a8c39b73c97814.svn-base --- a/.svn/pristine/18/18aaa55b2d77150962ade434e4a8c39b73c97814.svn-base Mon Mar 17 08:54:02 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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/18/18af7ca4dac0c217561ab69b8406264b81adb03a.svn-base --- a/.svn/pristine/18/18af7ca4dac0c217561ab69b8406264b81adb03a.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +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 UserCustomField < CustomField - def type_name - :label_user_plural - end -end - diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/19/190e5bea2d5dc9984a86fa22ae8cc4dd749898ab.svn-base --- a/.svn/pristine/19/190e5bea2d5dc9984a86fa22ae8cc4dd749898ab.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +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 Search - - mattr_accessor :available_search_types - - @@available_search_types = [] - - class << self - def map(&block) - yield self - end - - # Registers a search provider - def register(search_type, options={}) - search_type = search_type.to_s - @@available_search_types << search_type unless @@available_search_types.include?(search_type) - end - end - - module Controller - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - @@default_search_scopes = Hash.new {|hash, key| hash[key] = {:default => nil, :actions => {}}} - mattr_accessor :default_search_scopes - - # Set the default search scope for a controller or specific actions - # Examples: - # * search_scope :issues # => sets the search scope to :issues for the whole controller - # * search_scope :issues, :only => :index - # * search_scope :issues, :only => [:index, :show] - def default_search_scope(id, options = {}) - if actions = options[:only] - actions = [] << actions unless actions.is_a?(Array) - actions.each {|a| default_search_scopes[controller_name.to_sym][:actions][a.to_sym] = id.to_s} - else - default_search_scopes[controller_name.to_sym][:default] = id.to_s - end - end - end - - def default_search_scopes - self.class.default_search_scopes - end - - # Returns the default search scope according to the current action - def default_search_scope - @default_search_scope ||= default_search_scopes[controller_name.to_sym][:actions][action_name.to_sym] || - default_search_scopes[controller_name.to_sym][:default] - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/19/191c54aa364bc303830fb03453b541edfd9e5945.svn-base --- a/.svn/pristine/19/191c54aa364bc303830fb03453b541edfd9e5945.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +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 Configuration - - # Configuration default values - @defaults = { - 'email_delivery' => nil, - 'max_concurrent_ajax_uploads' => 2 - } - - @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 e248c7af89ec -r b450a9d58aed .svn/pristine/1a/1a3135bf503a709c26b5156223514133ddf98de3.svn-base --- a/.svn/pristine/1a/1a3135bf503a709c26b5156223514133ddf98de3.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +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 '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 e248c7af89ec -r b450a9d58aed .svn/pristine/1a/1a57484f351c2899093aa4b926665fc8327667ab.svn-base --- a/.svn/pristine/1a/1a57484f351c2899093aa4b926665fc8327667ab.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/1a/1a5ac9a1d7cbc1a58738649fabccca79dc7633e5.svn-base --- a/.svn/pristine/1a/1a5ac9a1d7cbc1a58738649fabccca79dc7633e5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +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 RoutingWikisTest < ActionController::IntegrationTest - def test_wikis_plural_admin_setup - ["get", "post"].each do |method| - assert_routing( - { :method => method, :path => "/projects/ladida/wiki/destroy" }, - { :controller => 'wikis', :action => 'destroy', :id => 'ladida' } - ) - end - assert_routing( - { :method => 'post', :path => "/projects/ladida/wiki" }, - { :controller => 'wikis', :action => 'edit', :id => 'ladida' } - ) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/1a/1a8020fad87f2bec2c86c62dc30a6bf033a7c796.svn-base --- a/.svn/pristine/1a/1a8020fad87f2bec2c86c62dc30a6bf033a7c796.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1268 +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 ApplicationHelperTest < ActionView::TestCase - include Redmine::I18n - include ERB::Util - include Rails.application.routes.url_helpers - - 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 - @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82" - if @russian_test.respond_to?(:force_encoding) - @russian_test.force_encoding('UTF-8') - end - end - - test "#link_to_if_authorized for authorized user 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/actionr', - {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) - assert_match /href/, response - end - - test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do - User.current = User.find_by_login('dlopper') - @project = Project.find('private-child') - issue = @project.issues.first - assert !issue.visible? - - response = link_to_if_authorized('Never displayed', - {:controller => 'issues', :action => 'show', :id => issue}) - assert_nil response - 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>', - # invalid urls - 'http://' => 'http://', - 'www.' => 'www.', - 'test-www.bar.com' => 'test-www.bar.com', - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - end - - if 'ruby'.respond_to?(:encoding) - def test_auto_links_with_non_ascii_characters - to_test = { - "http://foo.bar/#{@russian_test}" => - %|http://foo.bar/#{@russian_test}| - } - 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 - to_test = { - 'test@foo.bar' => '', - 'test@www.foo.bar' => '', - } - to_test.each { |text, result| assert_equal "

    #{result}

    ", textilizable(text) } - 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.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/#{@russian_test}| => - %|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.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') - note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, - :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') - note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, - :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') - - revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, - :class => 'changeset', :title => 'My very first commit do not escaping #<>&') - revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, - :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') - - changeset_link2 = link_to('691322a8eb01e11fd7', - {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, - :class => 'changeset', :title => 'My very first commit do not escaping #<>&') - - document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1}, - :class => 'document') - - 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' - source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file' - - export_url = '/projects/ecookbook/repository/raw/some/file' - export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file' - export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext' - export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext' - export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file' - - to_test = { - # tickets - '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.", - # ticket notes - '#3-14' => note_link, - '#3#note-14' => note_link2, - # should not ignore leading zero - '#03' => '#03', - # changesets - 'r1' => revision_link, - 'r1.' => "#{revision_link}.", - 'r1, r2' => "#{revision_link}, #{revision_link2}", - 'r1,r2' => "#{revision_link},#{revision_link2}", - 'commit:691322a8eb01e11fd7' => changeset_link2, - # documents - 'document#1' => document_link, - 'document:"Test document"' => document_link, - # 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@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'), - 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'), - 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'), - 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'), - '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'), - 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'), - # forum - 'forum#2' => link_to('Discussion', board_url, :class => 'board'), - 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'), - # 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 - to_test = { - 'attachment:error281.txt' => 'error281.txt' - } - 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 - russian_eacape = CGI.escape(@russian_test) - 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##{@russian_test}|#{@russian_test}]]" => - %|#{@russian_test}|, - # 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 -->

    ", - " -<%= yield :header_tags -%> - - -
    -
    -
    -
    -
    - <%= render_menu :account_menu -%> -
    - <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}".html_safe, :id => 'loggedas') if User.current.logged? %> - <%= render_menu :top_menu if User.current.logged? || !Setting.login_required? -%> -
    - - - - -
    - - - - - -
    -
    -<%= call_hook :view_layouts_base_body_bottom %> - - diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/58/584923323e8dc0fd68ba4e96b679332b9ee9eb19.svn-base --- a/.svn/pristine/58/584923323e8dc0fd68ba4e96b679332b9ee9eb19.svn-base Mon Mar 17 08:54:02 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. - -require File.expand_path('../../../test_helper', __FILE__) - -class Redmine::ApiTest::IssueStatusesTest < Redmine::ApiTest::Base - fixtures :issue_statuses - - def setup - Setting.rest_api_enabled = '1' - end - - test "GET /issue_statuses.xml 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 diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/58/58ad4095c1a6118f6d53fd92a945cd194604e422.svn-base --- a/.svn/pristine/58/58ad4095c1a6118f6d53fd92a945cd194604e422.svn-base Mon Mar 17 08:54:02 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. - -require File.expand_path('../../test_helper', __FILE__) - -class PreviewsControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :journals, :journal_details, - :news - - def test_preview_new_issue - @request.session[:user_id] = 2 - post :issue, :project_id => '1', :issue => {:description => 'Foo'} - assert_response :success - assert_template 'preview' - assert_not_nil assigns(:description) - end - - def test_preview_issue_notes - @request.session[:user_id] = 2 - post :issue, :project_id => '1', :id => 1, - :issue => {:description => Issue.find(1).description, :notes => 'Foo'} - assert_response :success - assert_template 'preview' - assert_not_nil assigns(:notes) - end - - def test_preview_journal_notes_for_update - @request.session[:user_id] = 2 - post :issue, :project_id => '1', :id => 1, :notes => 'Foo' - assert_response :success - assert_template 'preview' - assert_not_nil assigns(:notes) - assert_tag :p, :content => 'Foo' - end - - def test_preview_new_news - get :news, :project_id => 1, - :news => {:title => '', - :description => 'News description', - :summary => ''} - assert_response :success - assert_template 'common/_preview' - assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' }, - :content => /News description/ - end - - def test_existing_new_news - get :news, :project_id => 1, :id => 2, - :news => {:title => '', - :description => 'News description', - :summary => ''} - assert_response :success - assert_template 'common/_preview' - assert_equal News.find(2), assigns(:previewed) - assert_not_nil assigns(:attachments) - - assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' }, - :content => /News description/ - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/59/59560647b1b38d17ca5eda58f2c560506ef65f44.svn-base --- a/.svn/pristine/59/59560647b1b38d17ca5eda58f2c560506ef65f44.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +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 IssuePriority < Enumeration - has_many :issues, :foreign_key => 'priority_id' - - after_destroy {|priority| priority.class.compute_position_names} - after_save {|priority| priority.class.compute_position_names if priority.position_changed? && priority.position} - - OptionName = :enumeration_issue_priorities - - def option_name - OptionName - end - - def objects_count - issues.count - end - - def transfer_relations(to) - issues.update_all("priority_id = #{to.id}") - end - - def css_classes - "priority-#{id} priority-#{position_name}" - end - - # Clears position_name for all priorities - # Called from migration 20121026003537_populate_enumerations_position_name - def self.clear_position_names - update_all :position_name => nil - end - - # Updates position_name for active priorities - # Called from migration 20121026003537_populate_enumerations_position_name - def self.compute_position_names - priorities = where(:active => true).all.sort_by(&:position) - if priorities.any? - default = priorities.detect(&:is_default?) || priorities[(priorities.size - 1) / 2] - priorities.each_with_index do |priority, index| - name = case - when priority.position == default.position - "default" - when priority.position < default.position - index == 0 ? "lowest" : "low#{index+1}" - else - index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}" - end - - update_all({:position_name => name}, :id => priority.id) - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/59/5957c73ec3e45b1dba5f2bd95614bcd541de4b15.svn-base --- a/.svn/pristine/59/5957c73ec3e45b1dba5f2bd95614bcd541de4b15.svn-base Mon Mar 17 08:54:02 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. - -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 e248c7af89ec -r b450a9d58aed .svn/pristine/5b/5b5912958e71fec54e66733c9c7981555c23902f.svn-base --- a/.svn/pristine/5b/5b5912958e71fec54e66733c9c7981555c23902f.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +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::HttpBasicLoginWithApiTokenTest < Redmine::ApiTest::Base - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules - - def setup - Setting.rest_api_enabled = '1' - Setting.login_required = '1' - end - - def teardown - Setting.rest_api_enabled = '0' - Setting.login_required = '0' - end - - # Using the NewsController because it's a simple API. - context "get /news" do - - context "in :xml format" do - should_allow_http_basic_auth_with_key(:get, "/news.xml") - end - - context "in :json format" do - should_allow_http_basic_auth_with_key(:get, "/news.json") - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5b/5b67cbc43876349cb50763840008cc0544dfb9b5.svn-base --- a/.svn/pristine/5b/5b67cbc43876349cb50763840008cc0544dfb9b5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +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 GroupTest < ActiveSupport::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, - :projects_trackers, - :roles, - :member_roles, - :members, - :groups_users - - include Redmine::I18n - - def test_create - g = Group.new(:name => 'New group') - assert g.save - g.reload - assert_equal 'New group', g.name - end - - def test_name_should_accept_255_characters - name = 'a' * 255 - g = Group.new(:name => name) - assert g.save - g.reload - assert_equal name, g.name - end - - def test_blank_name_error_message - set_language_if_valid 'en' - g = Group.new - assert !g.save - assert_include "Name can't be blank", g.errors.full_messages - end - - def test_blank_name_error_message_fr - set_language_if_valid 'fr' - str = "Nom doit \xc3\xaatre renseign\xc3\xa9(e)" - str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) - g = Group.new - assert !g.save - assert_include str, g.errors.full_messages - end - - def test_group_roles_should_be_given_to_added_user - group = Group.find(11) - user = User.find(9) - project = Project.first - - Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) - group.users << user - assert user.member_of?(project) - end - - def test_new_roles_should_be_given_to_existing_user - group = Group.find(11) - user = User.find(9) - project = Project.first - - group.users << user - m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) - assert user.member_of?(project) - end - - def test_user_roles_should_updated_when_updating_user_ids - group = Group.find(11) - user = User.find(9) - project = Project.first - - Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) - group.user_ids = [user.id] - group.save! - assert User.find(9).member_of?(project) - - group.user_ids = [1] - group.save! - assert !User.find(9).member_of?(project) - end - - def test_user_roles_should_updated_when_updating_group_roles - group = Group.find(11) - user = User.find(9) - project = Project.first - group.users << user - m = Member.create!(:principal => group, :project => project, :role_ids => [1]) - assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort - - m.role_ids = [1, 2] - assert_equal [1, 2], user.reload.roles_for_project(project).collect(&:id).sort - - m.role_ids = [2] - assert_equal [2], user.reload.roles_for_project(project).collect(&:id).sort - - m.role_ids = [1] - assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort - end - - def test_user_memberships_should_be_removed_when_removing_group_membership - assert User.find(8).member_of?(Project.find(5)) - Member.find_by_project_id_and_user_id(5, 10).destroy - assert !User.find(8).member_of?(Project.find(5)) - end - - def test_user_roles_should_be_removed_when_removing_user_from_group - assert User.find(8).member_of?(Project.find(5)) - User.find(8).groups = [] - assert !User.find(8).member_of?(Project.find(5)) - end - - def test_destroy_should_unassign_issues - group = Group.first - Issue.update_all(["assigned_to_id = ?", group.id], 'id = 1') - - assert group.destroy - assert group.destroyed? - - assert_equal nil, Issue.find(1).assigned_to_id - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5b/5bd58dbcc9700e9a0143a2bd31d516491119c122.svn-base --- a/.svn/pristine/5b/5bd58dbcc9700e9a0143a2bd31d516491119c122.svn-base Mon Mar 17 08:54:02 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 '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 e248c7af89ec -r b450a9d58aed .svn/pristine/5c/5c1734e1eca1c54bac30b17ddeecdedbc05caf30.svn-base --- a/.svn/pristine/5c/5c1734e1eca1c54bac30b17ddeecdedbc05caf30.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +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. - - -namespace :db do - desc 'Encrypts SCM and LDAP passwords in the database.' - task :encrypt => :environment do - unless (Repository.encrypt_all(:password) && - AuthSource.encrypt_all(:account_password)) - raise "Some objects could not be saved after encryption, update was rollback'ed." - end - end - - desc 'Decrypts SCM and LDAP passwords in the database.' - task :decrypt => :environment do - unless (Repository.decrypt_all(:password) && - AuthSource.decrypt_all(:account_password)) - raise "Some objects could not be saved after decryption, update was rollback'ed." - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5c/5cefc982f83157ee64c82964ccc1a3e15fc78cb5.svn-base --- a/.svn/pristine/5c/5cefc982f83157ee64c82964ccc1a3e15fc78cb5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,356 +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. - -# The WikiController follows the Rails REST controller pattern but with -# a few differences -# -# * index - shows a list of WikiPages grouped by page or date -# * new - not used -# * create - not used -# * show - will also show the form for creating a new wiki page -# * edit - used to edit an existing or new page -# * update - used to save a wiki page update to the database, including new pages -# * destroy - normal -# -# Other member and collection methods are also used -# -# TODO: still being worked on -class WikiController < ApplicationController - default_search_scope :wiki_pages - before_filter :find_wiki, :authorize - before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] - before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] - accept_api_auth :index, :show, :update, :destroy - before_filter :find_attachments, :only => [:preview] - - helper :attachments - include AttachmentsHelper - helper :watchers - include Redmine::Export::PDF - - # List of pages, sorted alphabetically and by parent (hierarchy) - def index - load_pages_for_index - - respond_to do |format| - format.html { - @pages_by_parent_id = @pages.group_by(&:parent_id) - } - format.api - end - end - - # List of page, by last update - def date_index - load_pages_for_index - @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} - end - - # display a page (in editing mode if it doesn't exist) - def show - if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) - deny_access - return - end - @content = @page.content_for_version(params[:version]) - if @content.nil? - if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? - edit - render :action => 'edit' - else - render_404 - end - return - end - if User.current.allowed_to?(:export_wiki_pages, @project) - if params[:format] == 'pdf' - send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") - return - elsif params[:format] == 'html' - export = render_to_string :action => 'export', :layout => false - send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") - return - elsif params[:format] == 'txt' - send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") - return - end - end - @editable = editable? - @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) && - @content.current_version? && - Redmine::WikiFormatting.supports_section_edit? - - respond_to do |format| - format.html - format.api - end - end - - # edit an existing page or a new one - def edit - return render_403 unless editable? - if @page.new_record? - if params[:parent].present? - @page.parent = @page.wiki.find_page(params[:parent].to_s) - end - end - - @content = @page.content_for_version(params[:version]) - @content ||= WikiContent.new(:page => @page) - @content.text = initial_page_content(@page) if @content.text.blank? - # don't keep previous comment - @content.comments = nil - - # To prevent StaleObjectError exception when reverting to a previous version - @content.version = @page.content.version if @page.content - - @text = @content.text - if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? - @section = params[:section].to_i - @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section) - render_404 if @text.blank? - end - end - - # Creates a new page or updates an existing one - def update - return render_403 unless editable? - was_new_page = @page.new_record? - @page.safe_attributes = params[:wiki_page] - - @content = @page.content || WikiContent.new(:page => @page) - content_params = params[:content] - if content_params.nil? && params[:wiki_page].is_a?(Hash) - content_params = params[:wiki_page].slice(:text, :comments, :version) - end - content_params ||= {} - - @content.comments = content_params[:comments] - @text = content_params[:text] - if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? - @section = params[:section].to_i - @section_hash = params[:section_hash] - @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash) - else - @content.version = content_params[:version] if content_params[:version] - @content.text = @text - end - @content.author = User.current - - if @page.save_with_content(@content) - attachments = Attachment.attach_files(@page, params[:attachments]) - render_attachment_warning_if_needed(@page) - call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) - - respond_to do |format| - format.html { - anchor = @section ? "section-#{@section}" : nil - redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor) - } - format.api { - if was_new_page - render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) - else - render_api_ok - end - } - end - else - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@content) } - end - end - - rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError - # Optimistic locking exception - respond_to do |format| - format.html { - flash.now[:error] = l(:notice_locking_conflict) - render :action => 'edit' - } - format.api { render_api_head :conflict } - end - rescue ActiveRecord::RecordNotSaved - respond_to do |format| - format.html { render :action => 'edit' } - format.api { render_validation_errors(@content) } - end - end - - # rename a page - def rename - return render_403 unless editable? - @page.redirect_existing_links = true - # used to display the *original* title if some AR validation errors occur - @original_title = @page.pretty_title - if request.post? && @page.update_attributes(params[:wiki_page]) - flash[:notice] = l(:notice_successful_update) - redirect_to project_wiki_page_path(@project, @page.title) - end - end - - def protect - @page.update_attribute :protected, params[:protected] - redirect_to project_wiki_page_path(@project, @page.title) - end - - # show page history - def history - @version_count = @page.content.versions.count - @version_pages = Paginator.new @version_count, per_page_option, params['page'] - # don't load text - @versions = @page.content.versions. - select("id, author_id, comments, updated_on, version"). - reorder('version DESC'). - limit(@version_pages.per_page + 1). - offset(@version_pages.offset). - all - - render :layout => false if request.xhr? - end - - def diff - @diff = @page.diff(params[:version], params[:version_from]) - render_404 unless @diff - end - - def annotate - @annotate = @page.annotate(params[:version]) - render_404 unless @annotate - end - - # Removes a wiki page and its history - # Children can be either set as root pages, removed or reassigned to another parent page - def destroy - return render_403 unless editable? - - @descendants_count = @page.descendants.size - if @descendants_count > 0 - case params[:todo] - when 'nullify' - # Nothing to do - when 'destroy' - # Removes all its descendants - @page.descendants.each(&:destroy) - when 'reassign' - # Reassign children to another parent page - reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i) - return unless reassign_to - @page.children.each do |child| - child.update_attribute(:parent, reassign_to) - end - else - @reassignable_to = @wiki.pages - @page.self_and_descendants - # display the destroy form if it's a user request - return unless api_request? - end - end - @page.destroy - respond_to do |format| - format.html { redirect_to project_wiki_index_path(@project) } - format.api { render_api_ok } - end - end - - def destroy_version - return render_403 unless editable? - - @content = @page.content_for_version(params[:version]) - @content.destroy - redirect_to_referer_or history_project_wiki_page_path(@project, @page.title) - end - - # Export wiki to a single pdf or html file - def export - @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}]) - respond_to do |format| - format.html { - export = render_to_string :action => 'export_multiple', :layout => false - send_data(export, :type => 'text/html', :filename => "wiki.html") - } - format.pdf { - send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf") - } - end - end - - def preview - page = @wiki.find_page(params[:id]) - # page is nil when previewing a new page - return render_403 unless page.nil? || editable?(page) - if page - @attachments += page.attachments - @previewed = page.content - end - @text = params[:content][:text] - render :partial => 'common/preview' - end - - def add_attachment - return render_403 unless editable? - attachments = Attachment.attach_files(@page, params[:attachments]) - render_attachment_warning_if_needed(@page) - redirect_to :action => 'show', :id => @page.title, :project_id => @project - end - -private - - def find_wiki - @project = Project.find(params[:project_id]) - @wiki = @project.wiki - render_404 unless @wiki - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Finds the requested page or a new page if it doesn't exist - def find_existing_or_new_page - @page = @wiki.find_or_new_page(params[:id]) - if @wiki.page_found_with_redirect? - redirect_to params.update(:id => @page.title) - end - end - - # Finds the requested page and returns a 404 error if it doesn't exist - def find_existing_page - @page = @wiki.find_page(params[:id]) - if @page.nil? - render_404 - return - end - if @wiki.page_found_with_redirect? - redirect_to params.update(:id => @page.title) - end - end - - # Returns true if the current user is allowed to edit the page, otherwise false - def editable?(page = @page) - page.editable_by?(User.current) - end - - # Returns the default content of a new wiki page - def initial_page_content(page) - helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) - extend helper unless self.instance_of?(helper) - helper.instance_method(:initial_page_content).bind(self).call(page) - end - - def load_pages_for_index - @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5d/5d26bd43e6982490a87abc50362d543225e9666c.svn-base --- a/.svn/pristine/5d/5d26bd43e6982490a87abc50362d543225e9666c.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +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 Change < ActiveRecord::Base - belongs_to :changeset - - validates_presence_of :changeset_id, :action, :path - before_save :init_path - before_validation :replace_invalid_utf8_of_path - - def relative_path - changeset.repository.relative_path(path) - end - - def replace_invalid_utf8_of_path - self.path = Redmine::CodesetUtil.replace_invalid_utf8(self.path) - self.from_path = Redmine::CodesetUtil.replace_invalid_utf8(self.from_path) - end - - def init_path - self.path ||= "" - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5f/5f34122110535f3ec5de210020321433576b63f3.svn-base --- a/.svn/pristine/5f/5f34122110535f3ec5de210020321433576b63f3.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +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 DefaultData - class DataAlreadyLoaded < Exception; end - - module Loader - include Redmine::I18n - - class << self - # Returns true if no data is already loaded in the database - # otherwise false - def no_data? - !Role.where(:builtin => 0).exists? && - !Tracker.exists? && - !IssueStatus.exists? && - !Enumeration.exists? - end - - # Loads the default data - # Raises a RecordNotSaved exception if something goes wrong - def load(lang=nil) - raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data? - set_language_if_valid(lang) - - Role.transaction do - # Roles - manager = Role.create! :name => l(:default_role_manager), - :issues_visibility => 'all', - :position => 1 - manager.permissions = manager.setable_permissions.collect {|p| p.name} - manager.save! - - developer = Role.create! :name => l(:default_role_developer), - :position => 2, - :permissions => [:manage_versions, - :manage_categories, - :view_issues, - :add_issues, - :edit_issues, - :view_private_notes, - :set_notes_private, - :manage_issue_relations, - :manage_subtasks, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :log_time, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :edit_wiki_pages, - :delete_wiki_pages, - :add_messages, - :edit_own_messages, - :view_files, - :manage_files, - :browse_repository, - :view_changesets, - :commit_access, - :manage_related_issues] - - reporter = Role.create! :name => l(:default_role_reporter), - :position => 3, - :permissions => [:view_issues, - :add_issues, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :log_time, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :add_messages, - :edit_own_messages, - :view_files, - :browse_repository, - :view_changesets] - - Role.non_member.update_attribute :permissions, [:view_issues, - :add_issues, - :add_issue_notes, - :save_queries, - :view_gantt, - :view_calendar, - :view_time_entries, - :comment_news, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :add_messages, - :view_files, - :browse_repository, - :view_changesets] - - Role.anonymous.update_attribute :permissions, [:view_issues, - :view_gantt, - :view_calendar, - :view_time_entries, - :view_documents, - :view_wiki_pages, - :view_wiki_edits, - :view_files, - :browse_repository, - :view_changesets] - - # Trackers - Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1) - Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2) - Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3) - - # Issue statuses - new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1) - in_progress = IssueStatus.create!(:name => l(:default_issue_status_in_progress), :is_closed => false, :is_default => false, :position => 2) - resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3) - feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4) - closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5) - rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6) - - # Workflow - Tracker.all.each { |t| - IssueStatus.all.each { |os| - IssueStatus.all.each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - } - - Tracker.all.each { |t| - [new, in_progress, resolved, feedback].each { |os| - [in_progress, resolved, feedback, closed].each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - } - - Tracker.all.each { |t| - [new, in_progress, resolved, feedback].each { |os| - [closed].each { |ns| - WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns - } - } - WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id) - } - - # Enumerations - IssuePriority.create!(:name => l(:default_priority_low), :position => 1) - IssuePriority.create!(:name => l(:default_priority_normal), :position => 2, :is_default => true) - IssuePriority.create!(:name => l(:default_priority_high), :position => 3) - IssuePriority.create!(:name => l(:default_priority_urgent), :position => 4) - IssuePriority.create!(:name => l(:default_priority_immediate), :position => 5) - - DocumentCategory.create!(:name => l(:default_doc_category_user), :position => 1) - DocumentCategory.create!(:name => l(:default_doc_category_tech), :position => 2) - - TimeEntryActivity.create!(:name => l(:default_activity_design), :position => 1) - TimeEntryActivity.create!(:name => l(:default_activity_development), :position => 2) - end - true - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/5f/5f91c8ec766f59461497fadde2dab5afeb5e2ac6.svn-base --- a/.svn/pristine/5f/5f91c8ec766f59461497fadde2dab5afeb5e2ac6.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +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::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 e248c7af89ec -r b450a9d58aed .svn/pristine/60/60a95a1966b625bd87358ca8fc028478e501f2bb.svn-base --- a/.svn/pristine/60/60a95a1966b625bd87358ca8fc028478e501f2bb.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,170 +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::GroupsTest < Redmine::ApiTest::Base - fixtures :users, :groups_users - - def setup - Setting.rest_api_enabled = '1' - end - - test "GET /groups.xml should require authentication" do - get '/groups.xml' - assert_response 401 - end - - test "GET /groups.xml should return groups" do - get '/groups.xml', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type - - assert_select 'groups' do - assert_select 'group' do - assert_select 'name', :text => 'A Team' - assert_select 'id', :text => '10' - end - end - end - - test "GET /groups.json should require authentication" do - get '/groups.json' - assert_response 401 - end - - test "GET /groups.json should return groups" do - get '/groups.json', {}, credentials('admin') - assert_response :success - assert_equal 'application/json', response.content_type - - json = MultiJson.load(response.body) - groups = json['groups'] - assert_kind_of Array, groups - group = groups.detect {|g| g['name'] == 'A Team'} - assert_not_nil group - assert_equal({'id' => 10, 'name' => 'A Team'}, group) - end - - test "GET /groups/:id.xml should return the group with its users" do - get '/groups/10.xml', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type - - assert_select 'group' do - assert_select 'name', :text => 'A Team' - assert_select 'id', :text => '10' - end - end - - test "GET /groups/:id.xml should include users if requested" do - get '/groups/10.xml?include=users', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type - - assert_select 'group' do - assert_select 'users' do - assert_select 'user', Group.find(10).users.count - assert_select 'user[id=8]' - end - end - end - - test "GET /groups/:id.xml include memberships if requested" do - get '/groups/10.xml?include=memberships', {}, credentials('admin') - assert_response :success - assert_equal 'application/xml', response.content_type - - assert_select 'group' do - assert_select 'memberships' - end - end - - test "POST /groups.xml with valid parameters should create the group" do - assert_difference('Group.count') do - post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin') - assert_response :created - assert_equal 'application/xml', response.content_type - end - - group = Group.order('id DESC').first - assert_equal 'Test', group.name - assert_equal [2, 3], group.users.map(&:id).sort - - assert_select 'group' do - assert_select 'name', :text => 'Test' - end - end - - test "POST /groups.xml with invalid parameters should return errors" do - assert_no_difference('Group.count') do - post '/groups.xml', {:group => {:name => ''}}, credentials('admin') - end - assert_response :unprocessable_entity - assert_equal 'application/xml', response.content_type - - assert_select 'errors' do - assert_select 'error', :text => /Name can't be blank/ - end - end - - test "PUT /groups/:id.xml with valid parameters should update the group" do - put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - - group = Group.find(10) - assert_equal 'New name', group.name - assert_equal [2, 3], group.users.map(&:id).sort - end - - test "PUT /groups/:id.xml with invalid parameters should return errors" do - put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin') - assert_response :unprocessable_entity - assert_equal 'application/xml', response.content_type - - assert_select 'errors' do - assert_select 'error', :text => /Name can't be blank/ - end - end - - test "DELETE /groups/:id.xml should delete the group" do - assert_difference 'Group.count', -1 do - delete '/groups/10.xml', {}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - end - - test "POST /groups/:id/users.xml should add user to the group" do - assert_difference 'Group.find(10).users.count' do - post '/groups/10/users.xml', {:user_id => 5}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - assert_include User.find(5), Group.find(10).users - end - - test "DELETE /groups/:id/users/:user_id.xml should remove user from the group" do - assert_difference 'Group.find(10).users.count', -1 do - delete '/groups/10/users/8.xml', {}, credentials('admin') - assert_response :ok - assert_equal '', @response.body - end - assert_not_include User.find(8), Group.find(10).users - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/61/61f5175c584e56d932e6fd6922f31fce1b57acd4.svn-base --- a/.svn/pristine/61/61f5175c584e56d932e6fd6922f31fce1b57acd4.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/63/6303e4e27f22b7c0923d90722df3a66861c6f0f3.svn-base --- a/.svn/pristine/63/6303e4e27f22b7c0923d90722df3a66861c6f0f3.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,950 +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. - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/63/634ac7c9412839a15f45fce185c715e696a25baa.svn-base --- a/.svn/pristine/63/634ac7c9412839a15f45fce185c715e696a25baa.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,482 +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 UsersControllerTest < ActionController::TestCase - include Redmine::I18n - - fixtures :users, :projects, :members, :member_roles, :roles, - :custom_fields, :custom_values, :groups_users, - :auth_sources - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - assert_not_nil assigns(:users) - # active users only - assert_nil assigns(:users).detect {|u| !u.active?} - end - - def test_index_with_status_filter - get :index, :status => 3 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:users) - assert_equal [3], assigns(:users).map(&:status).uniq - end - - def test_index_with_name_filter - get :index, :name => 'john' - assert_response :success - assert_template 'index' - users = assigns(:users) - assert_not_nil users - assert_equal 1, users.size - assert_equal 'John', users.first.firstname - end - - def test_index_with_group_filter - get :index, :group_id => '10' - assert_response :success - assert_template 'index' - users = assigns(:users) - assert users.any? - assert_equal([], (users - Group.find(10).users)) - assert_select 'select[name=group_id]' do - assert_select 'option[value=10][selected=selected]' - end - end - - def test_show - @request.session[:user_id] = nil - get :show, :id => 2 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:user) - - assert_tag 'li', :content => /Phone number/ - end - - def test_show_should_not_display_hidden_custom_fields - @request.session[:user_id] = nil - UserCustomField.find_by_name('Phone number').update_attribute :visible, false - get :show, :id => 2 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:user) - - assert_no_tag 'li', :content => /Phone number/ - end - - def test_show_should_not_fail_when_custom_values_are_nil - user = User.find(2) - - # Create a custom field to illustrate the issue - custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text') - custom_value = user.custom_values.build(:custom_field => custom_field).save! - - get :show, :id => 2 - assert_response :success - end - - def test_show_inactive - @request.session[:user_id] = nil - get :show, :id => 5 - assert_response 404 - end - - def test_show_should_not_reveal_users_with_no_visible_activity_or_project - @request.session[:user_id] = nil - get :show, :id => 9 - assert_response 404 - end - - def test_show_inactive_by_admin - @request.session[:user_id] = 1 - get :show, :id => 5 - assert_response 200 - assert_not_nil assigns(:user) - end - - def test_show_displays_memberships_based_on_project_visibility - @request.session[:user_id] = 1 - get :show, :id => 2 - assert_response :success - memberships = assigns(:memberships) - assert_not_nil memberships - project_ids = memberships.map(&:project_id) - assert project_ids.include?(2) #private project admin can see - end - - def test_show_current_should_require_authentication - @request.session[:user_id] = nil - get :show, :id => 'current' - assert_response 302 - end - - def test_show_current - @request.session[:user_id] = 2 - get :show, :id => 'current' - assert_response :success - assert_template 'show' - assert_equal User.find(2), assigns(:user) - end - - def test_new - get :new - assert_response :success - assert_template :new - assert assigns(:user) - end - - def test_create - Setting.bcc_recipients = '1' - - assert_difference 'User.count' do - assert_difference 'ActionMailer::Base.deliveries.size' do - post :create, - :user => { - :firstname => 'John', - :lastname => 'Doe', - :login => 'jdoe', - :password => 'secret123', - :password_confirmation => 'secret123', - :mail => 'jdoe@gmail.com', - :mail_notification => 'none' - }, - :send_information => '1' - end - end - - user = User.first(:order => 'id DESC') - assert_redirected_to :controller => 'users', :action => 'edit', :id => user.id - - assert_equal 'John', user.firstname - assert_equal 'Doe', user.lastname - assert_equal 'jdoe', user.login - assert_equal 'jdoe@gmail.com', user.mail - assert_equal 'none', user.mail_notification - assert user.check_password?('secret123') - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal [user.mail], mail.bcc - assert_mail_body_match 'secret', mail - end - - def test_create_with_preferences - assert_difference 'User.count' do - post :create, - :user => { - :firstname => 'John', - :lastname => 'Doe', - :login => 'jdoe', - :password => 'secret123', - :password_confirmation => 'secret123', - :mail => 'jdoe@gmail.com', - :mail_notification => 'none' - }, - :pref => { - 'hide_mail' => '1', - 'time_zone' => 'Paris', - 'comments_sorting' => 'desc', - 'warn_on_leaving_unsaved' => '0' - } - end - user = User.first(:order => 'id DESC') - assert_equal 'jdoe', user.login - assert_equal true, user.pref.hide_mail - assert_equal 'Paris', user.pref.time_zone - assert_equal 'desc', user.pref[:comments_sorting] - assert_equal '0', user.pref[:warn_on_leaving_unsaved] - end - - def test_create_with_generate_password_should_email_the_password - assert_difference 'User.count' do - post :create, :user => { - :login => 'randompass', - :firstname => 'Random', - :lastname => 'Pass', - :mail => 'randompass@example.net', - :language => 'en', - :generate_password => '1', - :password => '', - :password_confirmation => '' - }, :send_information => 1 - end - user = User.order('id DESC').first - assert_equal 'randompass', user.login - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/) - assert m - password = m[1] - assert user.check_password?(password) - end - - def test_create_with_failure - assert_no_difference 'User.count' do - post :create, :user => {} - end - assert_response :success - assert_template 'new' - end - - def test_edit - get :edit, :id => 2 - assert_response :success - assert_template 'edit' - assert_equal User.find(2), assigns(:user) - end - - def test_update - ActionMailer::Base.deliveries.clear - put :update, :id => 2, - :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'}, - :pref => {:hide_mail => '1', :comments_sorting => 'desc'} - user = User.find(2) - assert_equal 'Changed', user.firstname - assert_equal 'only_assigned', user.mail_notification - assert_equal true, user.pref[:hide_mail] - assert_equal 'desc', user.pref[:comments_sorting] - assert ActionMailer::Base.deliveries.empty? - end - - def test_update_with_failure - assert_no_difference 'User.count' do - put :update, :id => 2, :user => {:firstname => ''} - end - assert_response :success - assert_template 'edit' - end - - def test_update_with_group_ids_should_assign_groups - put :update, :id => 2, :user => {:group_ids => ['10']} - user = User.find(2) - assert_equal [10], user.group_ids - end - - def test_update_with_activation_should_send_a_notification - u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr') - u.login = 'foo' - u.status = User::STATUS_REGISTERED - u.save! - ActionMailer::Base.deliveries.clear - Setting.bcc_recipients = '1' - - put :update, :id => u.id, :user => {:status => User::STATUS_ACTIVE} - assert u.reload.active? - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal ['foo.bar@somenet.foo'], mail.bcc - assert_mail_body_match ll('fr', :notice_account_activated), mail - end - - def test_update_with_password_change_should_send_a_notification - ActionMailer::Base.deliveries.clear - Setting.bcc_recipients = '1' - - put :update, :id => 2, :user => {:password => 'newpass123', :password_confirmation => 'newpass123'}, :send_information => '1' - u = User.find(2) - assert u.check_password?('newpass123') - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - assert_equal [u.mail], mail.bcc - assert_mail_body_match 'newpass123', mail - end - - def test_update_with_generate_password_should_email_the_password - ActionMailer::Base.deliveries.clear - Setting.bcc_recipients = '1' - - put :update, :id => 2, :user => { - :generate_password => '1', - :password => '', - :password_confirmation => '' - }, :send_information => '1' - - mail = ActionMailer::Base.deliveries.last - assert_not_nil mail - m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/) - assert m - password = m[1] - assert User.find(2).check_password?(password) - end - - def test_update_without_generate_password_should_not_change_password - put :update, :id => 2, :user => { - :firstname => 'changed', - :generate_password => '0', - :password => '', - :password_confirmation => '' - }, :send_information => '1' - - user = User.find(2) - assert_equal 'changed', user.firstname - assert user.check_password?('jsmith') - end - - def test_update_user_switchin_from_auth_source_to_password_authentication - # Configure as auth source - u = User.find(2) - u.auth_source = AuthSource.find(1) - u.save! - - put :update, :id => u.id, :user => {:auth_source_id => '', :password => 'newpass123', :password_confirmation => 'newpass123'} - - assert_equal nil, u.reload.auth_source - assert u.check_password?('newpass123') - end - - def test_update_notified_project - get :edit, :id => 2 - assert_response :success - assert_template 'edit' - u = User.find(2) - assert_equal [1, 2, 5], u.projects.collect{|p| p.id}.sort - assert_equal [1, 2, 5], u.notified_projects_ids.sort - assert_select 'input[name=?][value=?]', 'user[notified_project_ids][]', '1' - assert_equal 'all', u.mail_notification - put :update, :id => 2, - :user => { - :mail_notification => 'selected', - :notified_project_ids => [1, 2] - } - u = User.find(2) - assert_equal 'selected', u.mail_notification - assert_equal [1, 2], u.notified_projects_ids.sort - end - - def test_update_status_should_not_update_attributes - user = User.find(2) - user.pref[:no_self_notified] = '1' - user.pref.save - - put :update, :id => 2, :user => {:status => 3} - assert_response 302 - user = User.find(2) - assert_equal 3, user.status - assert_equal '1', user.pref[:no_self_notified] - end - - def test_destroy - assert_difference 'User.count', -1 do - delete :destroy, :id => 2 - end - assert_redirected_to '/users' - assert_nil User.find_by_id(2) - end - - def test_destroy_should_be_denied_for_non_admin_users - @request.session[:user_id] = 3 - - assert_no_difference 'User.count' do - get :destroy, :id => 2 - end - assert_response 403 - end - - def test_destroy_should_redirect_to_back_url_param - assert_difference 'User.count', -1 do - delete :destroy, :id => 2, :back_url => '/users?name=foo' - end - assert_redirected_to '/users?name=foo' - end - - def test_create_membership - assert_difference 'Member.count' do - post :edit_membership, :id => 7, :membership => { :project_id => 3, :role_ids => [2]} - end - assert_redirected_to :action => 'edit', :id => '7', :tab => 'memberships' - member = Member.first(:order => 'id DESC') - assert_equal User.find(7), member.principal - assert_equal [2], member.role_ids - assert_equal 3, member.project_id - end - - def test_create_membership_js_format - assert_difference 'Member.count' do - post :edit_membership, :id => 7, :membership => {:project_id => 3, :role_ids => [2]}, :format => 'js' - assert_response :success - assert_template 'edit_membership' - assert_equal 'text/javascript', response.content_type - end - member = Member.first(:order => 'id DESC') - assert_equal User.find(7), member.principal - assert_equal [2], member.role_ids - assert_equal 3, member.project_id - assert_include 'tab-content-memberships', response.body - end - - def test_create_membership_js_format_with_failure - assert_no_difference 'Member.count' do - post :edit_membership, :id => 7, :membership => {:project_id => 3}, :format => 'js' - assert_response :success - assert_template 'edit_membership' - assert_equal 'text/javascript', response.content_type - end - assert_include 'alert', response.body, "Alert message not sent" - assert_include 'Role can\\\'t be empty', response.body, "Error message not sent" - end - - def test_update_membership - assert_no_difference 'Member.count' do - put :edit_membership, :id => 2, :membership_id => 1, :membership => { :role_ids => [2]} - assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships' - end - assert_equal [2], Member.find(1).role_ids - end - - def test_update_membership_js_format - assert_no_difference 'Member.count' do - put :edit_membership, :id => 2, :membership_id => 1, :membership => {:role_ids => [2]}, :format => 'js' - assert_response :success - assert_template 'edit_membership' - assert_equal 'text/javascript', response.content_type - end - assert_equal [2], Member.find(1).role_ids - assert_include 'tab-content-memberships', response.body - end - - def test_destroy_membership - assert_difference 'Member.count', -1 do - delete :destroy_membership, :id => 2, :membership_id => 1 - end - assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships' - assert_nil Member.find_by_id(1) - end - - def test_destroy_membership_js_format - assert_difference 'Member.count', -1 do - delete :destroy_membership, :id => 2, :membership_id => 1, :format => 'js' - assert_response :success - assert_template 'destroy_membership' - assert_equal 'text/javascript', response.content_type - end - assert_nil Member.find_by_id(1) - assert_include 'tab-content-memberships', response.body - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/63/638ef428af64a93a5a54376e8e444c15761d3973.svn-base --- a/.svn/pristine/63/638ef428af64a93a5a54376e8e444c15761d3973.svn-base Mon Mar 17 08:54:02 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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/63/63f5d9f908a9cb71c1fdf7287c1a8cd3db047d81.svn-base --- a/.svn/pristine/63/63f5d9f908a9cb71c1fdf7287c1a8cd3db047d81.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +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 TrackersControllerTest < ActionController::TestCase - fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields - - def setup - User.current = nil - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - end - - def test_index_by_anonymous_should_redirect_to_login_form - @request.session[:user_id] = nil - get :index - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers' - end - - def test_index_by_user_should_respond_with_406 - @request.session[:user_id] = 2 - get :index - assert_response 406 - end - - def test_new - get :new - assert_response :success - assert_template 'new' - end - - def test_create - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } - end - assert_redirected_to :action => 'index' - tracker = Tracker.first(:order => 'id DESC') - assert_equal 'New tracker', tracker.name - assert_equal [1], tracker.project_ids.sort - assert_equal Tracker::CORE_FIELDS, tracker.core_fields - assert_equal [1, 6], tracker.custom_field_ids.sort - assert_equal 0, tracker.workflow_rules.count - end - - def create_with_disabled_core_fields - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker', :core_fields => ['assigned_to_id', 'fixed_version_id', ''] } - end - assert_redirected_to :action => 'index' - tracker = Tracker.first(:order => 'id DESC') - assert_equal 'New tracker', tracker.name - assert_equal %w(assigned_to_id fixed_version_id), tracker.core_fields - end - - def test_create_new_with_workflow_copy - assert_difference 'Tracker.count' do - post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1 - end - assert_redirected_to :action => 'index' - tracker = Tracker.find_by_name('New tracker') - assert_equal 0, tracker.projects.count - assert_equal Tracker.find(1).workflow_rules.count, tracker.workflow_rules.count - end - - def test_create_with_failure - assert_no_difference 'Tracker.count' do - post :create, :tracker => { :name => '', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } - end - assert_response :success - assert_template 'new' - assert_error_tag :content => /name can't be blank/i - end - - def test_edit - Tracker.find(1).project_ids = [1, 3] - - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '1', - :checked => 'checked' } - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '2', - :checked => nil } - - assert_tag :input, :attributes => { :name => 'tracker[project_ids][]', - :value => '', - :type => 'hidden'} - end - - def test_edit_should_check_core_fields - tracker = Tracker.find(1) - tracker.core_fields = %w(assigned_to_id fixed_version_id) - tracker.save! - - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - - assert_select 'input[name=?][value=assigned_to_id][checked=checked]', 'tracker[core_fields][]' - assert_select 'input[name=?][value=fixed_version_id][checked=checked]', 'tracker[core_fields][]' - - assert_select 'input[name=?][value=category_id]', 'tracker[core_fields][]' - assert_select 'input[name=?][value=category_id][checked=checked]', 'tracker[core_fields][]', 0 - - assert_select 'input[name=?][value=][type=hidden]', 'tracker[core_fields][]' - end - - def test_update - put :update, :id => 1, :tracker => { :name => 'Renamed', - :project_ids => ['1', '2', ''] } - assert_redirected_to :action => 'index' - assert_equal [1, 2], Tracker.find(1).project_ids.sort - end - - def test_update_without_projects - put :update, :id => 1, :tracker => { :name => 'Renamed', - :project_ids => [''] } - assert_redirected_to :action => 'index' - assert Tracker.find(1).project_ids.empty? - end - - def test_update_without_core_fields - put :update, :id => 1, :tracker => { :name => 'Renamed', :core_fields => [''] } - assert_redirected_to :action => 'index' - assert Tracker.find(1).core_fields.empty? - end - - def test_update_with_failure - put :update, :id => 1, :tracker => { :name => '' } - assert_response :success - assert_template 'edit' - assert_error_tag :content => /name can't be blank/i - end - - def test_move_lower - tracker = Tracker.find_by_position(1) - put :update, :id => 1, :tracker => { :move_to => 'lower' } - assert_equal 2, tracker.reload.position - end - - def test_destroy - tracker = Tracker.create!(:name => 'Destroyable') - assert_difference 'Tracker.count', -1 do - delete :destroy, :id => tracker.id - end - assert_redirected_to :action => 'index' - assert_nil flash[:error] - end - - def test_destroy_tracker_in_use - assert_no_difference 'Tracker.count' do - delete :destroy, :id => 1 - end - assert_redirected_to :action => 'index' - assert_not_nil flash[:error] - end - - def test_get_fields - get :fields - assert_response :success - assert_template 'fields' - - assert_select 'form' do - assert_select 'input[type=checkbox][name=?][value=assigned_to_id]', 'trackers[1][core_fields][]' - assert_select 'input[type=checkbox][name=?][value=2]', 'trackers[1][custom_field_ids][]' - - assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][core_fields][]' - assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][custom_field_ids][]' - end - end - - def test_post_fields - post :fields, :trackers => { - '1' => {'core_fields' => ['assigned_to_id', 'due_date', ''], 'custom_field_ids' => ['1', '2']}, - '2' => {'core_fields' => [''], 'custom_field_ids' => ['']} - } - assert_redirected_to '/trackers/fields' - - tracker = Tracker.find(1) - assert_equal %w(assigned_to_id due_date), tracker.core_fields - assert_equal [1, 2], tracker.custom_field_ids.sort - - tracker = Tracker.find(2) - assert_equal [], tracker.core_fields - assert_equal [], tracker.custom_field_ids.sort - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/64/6402c0873c916f8ca24a20adde0b93d3110a6089.svn-base --- a/.svn/pristine/64/6402c0873c916f8ca24a20adde0b93d3110a6089.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2013 Jean-Philippe Lang -# -# FileSystem adapter -# File written by Paul Rivier, at Demotera. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/abstract_adapter' -require 'find' - -module Redmine - module Scm - module Adapters - class FilesystemAdapter < AbstractAdapter - - class << self - def client_available - true - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = with_trailling_slash(url) - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - end - - def path_encoding - @path_encoding - end - - def format_path_ends(path, leading=true, trailling=true) - path = leading ? with_leading_slash(path) : - without_leading_slash(path) - trailling ? with_trailling_slash(path) : - without_trailling_slash(path) - end - - def info - info = Info.new({:root_url => target(), - :lastrev => nil - }) - info - rescue CommandFailed - return nil - end - - def entries(path="", identifier=nil, options={}) - entries = Entries.new - trgt_utf8 = target(path) - trgt = scm_iconv(@path_encoding, 'UTF-8', trgt_utf8) - Dir.new(trgt).each do |e1| - e_utf8 = scm_iconv('UTF-8', @path_encoding, e1) - next if e_utf8.blank? - relative_path_utf8 = format_path_ends( - (format_path_ends(path,false,true) + e_utf8),false,false) - t1_utf8 = target(relative_path_utf8) - t1 = scm_iconv(@path_encoding, 'UTF-8', t1_utf8) - relative_path = scm_iconv(@path_encoding, 'UTF-8', relative_path_utf8) - e1 = scm_iconv(@path_encoding, 'UTF-8', e_utf8) - if File.exist?(t1) and # paranoid test - %w{file directory}.include?(File.ftype(t1)) and # avoid special types - not File.basename(e1).match(/^\.+$/) # avoid . and .. - p1 = File.readable?(t1) ? relative_path : "" - utf_8_path = scm_iconv('UTF-8', @path_encoding, p1) - entries << - Entry.new({ :name => scm_iconv('UTF-8', @path_encoding, File.basename(e1)), - # below : list unreadable files, but dont link them. - :path => utf_8_path, - :kind => (File.directory?(t1) ? 'dir' : 'file'), - :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), - :lastrev => - Revision.new({:time => (File.mtime(t1)) }) - }) - end - end - entries.sort_by_name - rescue => err - logger.error "scm: filesystem: error: #{err.message}" - raise CommandFailed.new(err.message) - end - - def cat(path, identifier=nil) - p = scm_iconv(@path_encoding, 'UTF-8', target(path)) - File.new(p, "rb").read - rescue => err - logger.error "scm: filesystem: error: #{err.message}" - raise CommandFailed.new(err.message) - end - - private - - # AbstractAdapter::target is implicitly made to quote paths. - # Here we do not shell-out, so we do not want quotes. - def target(path=nil) - # Prevent the use of .. - if path and !path.match(/(^|\/)\.\.(\/|$)/) - return "#{self.url}#{without_leading_slash(path)}" - end - return self.url - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/64/6495ba1b375d76ae48a1a29718388eabb1d9a8c0.svn-base --- a/.svn/pristine/64/6495ba1b375d76ae48a1a29718388eabb1d9a8c0.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/64/64c4b3023914b8e9badc4f2743e75346c30f91ab.svn-base --- a/.svn/pristine/64/64c4b3023914b8e9badc4f2743e75346c30f91ab.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +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/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 e248c7af89ec -r b450a9d58aed .svn/pristine/65/6522becd35e6716c94ac60c2f3bd1780740bea87.svn-base --- a/.svn/pristine/65/6522becd35e6716c94ac60c2f3bd1780740bea87.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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 DocumentTest < ActiveSupport::TestCase - fixtures :projects, :enumerations, :documents, :attachments, - :enabled_modules, - :users, :members, :member_roles, :roles, - :groups_users - - def test_create - doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) - assert doc.save - end - - def test_create_should_send_email_notification - ActionMailer::Base.deliveries.clear - - with_settings :notified_events => %w(document_added) do - doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) - assert doc.save - end - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_create_with_default_category - # Sets a default category - e = Enumeration.find_by_name('Technical documentation') - e.update_attributes(:is_default => true) - - doc = Document.new(:project => Project.find(1), :title => 'New document') - assert_equal e, doc.category - assert doc.save - end - - def test_updated_on_with_attachments - d = Document.find(1) - assert d.attachments.any? - assert_equal d.attachments.map(&:created_on).max, d.updated_on - end - - def test_updated_on_without_attachments - d = Document.find(2) - assert d.attachments.empty? - assert_equal d.created_on, d.updated_on - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/65/6548ced1be2ac2971e9540ff4fe8979b3f03573e.svn-base --- a/.svn/pristine/65/6548ced1be2ac2971e9540ff4fe8979b3f03573e.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/65/65b88641ff8e4579cbf40b69a6bf7f5392a27740.svn-base --- a/.svn/pristine/65/65b88641ff8e4579cbf40b69a6bf7f5392a27740.svn-base Mon Mar 17 08:54:02 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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/66/662e86180d02fd07ea0b83b2c6615af46feab399.svn-base --- a/.svn/pristine/66/662e86180d02fd07ea0b83b2c6615af46feab399.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +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 IssueStatus < ActiveRecord::Base - before_destroy :check_integrity - has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id" - acts_as_list - - before_destroy :delete_workflow_rules - after_save :update_default - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true - - scope :sorted, lambda { order("#{table_name}.position ASC") } - scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - - def update_default - IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default? - end - - # Returns the default status for new issues - def self.default - where(:is_default => true).first - end - - # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+ - def self.update_issue_done_ratios - if Issue.use_status_for_done_ratio? - IssueStatus.where("default_done_ratio >= 0").all.each do |status| - Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id}) - end - end - - return Issue.use_status_for_done_ratio? - end - - # Returns an array of all statuses the given role can switch to - # Uses association cache when called more than one time - def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) - if roles && tracker - role_ids = roles.collect(&:id) - transitions = workflows.select do |w| - role_ids.include?(w.role_id) && - w.tracker_id == tracker.id && - ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee)) - end - transitions.map(&:new_status).compact.sort - else - [] - end - end - - # Same thing as above but uses a database query - # More efficient than the previous method if called just once - def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) - if roles.present? && tracker - conditions = "(author = :false AND assignee = :false)" - conditions << " OR author = :true" if author - conditions << " OR assignee = :true" if assignee - - workflows. - includes(:new_status). - where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", - {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} - ]).all. - map(&:new_status).compact.sort - else - [] - end - end - - def <=>(status) - position <=> status.position - end - - def to_s; name end - - private - - def check_integrity - raise "Can't delete status" if Issue.where(:status_id => id).any? - end - - # Deletes associated workflows - def delete_workflow_rules - WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}]) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/66/66834a4bdddfd399aac57e87263804a8152e50b5.svn-base --- a/.svn/pristine/66/66834a4bdddfd399aac57e87263804a8152e50b5.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +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 TimeEntryQueryTest < ActiveSupport::TestCase - fixtures :projects, :users, :enumerations - - def test_activity_filter_should_consider_system_and_project_activities - TimeEntry.delete_all - system = TimeEntryActivity.create!(:name => 'Foo') - override = TimeEntryActivity.create!(:name => 'Foo', :parent_id => system.id, :project_id => 1) - other = TimeEntryActivity.create!(:name => 'Bar') - TimeEntry.generate!(:activity => system, :hours => 1.0) - TimeEntry.generate!(:activity => override, :hours => 2.0) - TimeEntry.generate!(:activity => other, :hours => 4.0) - - query = TimeEntryQuery.new(:name => '_') - query.add_filter('activity_id', '=', [system.id.to_s]) - assert_equal 3.0, query.results_scope.sum(:hours) - - query = TimeEntryQuery.new(:name => '_') - query.add_filter('activity_id', '!', [system.id.to_s]) - assert_equal 4.0, query.results_scope.sum(:hours) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/67/67fb9a2c82ee9bb7b43a202b8b3be8deec0d41f4.svn-base --- a/.svn/pristine/67/67fb9a2c82ee9bb7b43a202b8b3be8deec0d41f4.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +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 SettingsController < ApplicationController - layout 'admin' - menu_item :plugins, :only => :plugin - - helper :queries - - before_filter :require_admin - - def index - edit - render :action => 'edit' - end - - def edit - @notifiables = Redmine::Notifiable.all - if request.post? && params[:settings] && params[:settings].is_a?(Hash) - settings = (params[:settings] || {}).dup.symbolize_keys - settings.each do |name, value| - Setting.set_from_params name, value - end - flash[:notice] = l(:notice_successful_update) - redirect_to settings_path(:tab => params[:tab]) - else - @options = {} - user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} - @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]} - @deliveries = ActionMailer::Base.perform_deliveries - - @guessed_host_and_path = request.host_with_port.dup - @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? - - @commit_update_keywords = Setting.commit_update_keywords.dup - @commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any? - - Redmine::Themes.rescan - end - end - - def plugin - @plugin = Redmine::Plugin.find(params[:id]) - unless @plugin.configurable? - render_404 - return - end - - if request.post? - Setting.send "plugin_#{@plugin.id}=", params[:settings] - flash[:notice] = l(:notice_successful_update) - redirect_to plugin_settings_path(@plugin) - else - @partial = @plugin.settings[:partial] - @settings = Setting.send "plugin_#{@plugin.id}" - end - rescue Redmine::PluginNotFound - render_404 - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/68/68cec9b340a32b5033a24e6ea73291b5b640c745.svn-base --- a/.svn/pristine/68/68cec9b340a32b5033a24e6ea73291b5b640c745.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +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 CommentsController < ApplicationController - default_search_scope :news - model_object News - before_filter :find_model_object - before_filter :find_project_from_association - before_filter :authorize - - def create - raise Unauthorized unless @news.commentable? - - @comment = Comment.new - @comment.safe_attributes = params[:comment] - @comment.author = User.current - if @news.comments << @comment - flash[:notice] = l(:label_comment_added) - end - - redirect_to news_path(@news) - end - - def destroy - @news.comments.find(params[:comment_id]).destroy - redirect_to news_path(@news) - end - - private - - # ApplicationController's find_model_object sets it based on the controller - # name so it needs to be overriden and set to @news instead - def find_model_object - super - @news = @object - @comment = nil - @news - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/69/69aad9646672b101f4780577efca970e899cd21a.svn-base --- a/.svn/pristine/69/69aad9646672b101f4780577efca970e899cd21a.svn-base Mon Mar 17 08:54:02 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__) -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 e248c7af89ec -r b450a9d58aed .svn/pristine/6a/6a96b935778cbb8f7a670a2735694f941fa3d694.svn-base --- a/.svn/pristine/6a/6a96b935778cbb8f7a670a2735694f941fa3d694.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +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 Journal < ActiveRecord::Base - belongs_to :journalized, :polymorphic => true - # added as a quick fix to allow eager loading of the polymorphic association - # since always associated to an issue, for now - belongs_to :issue, :foreign_key => :journalized_id - - belongs_to :user - has_many :details, :class_name => "JournalDetail", :dependent => :delete_all - attr_accessor :indice - - acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, - :description => :notes, - :author => :user, - :group => :issue, - :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, - :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} - - acts_as_activity_provider :type => 'issues', - :author_key => :user_id, - :find_options => {:include => [{:issue => :project}, :details, :user], - :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + - " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} - - before_create :split_private_notes - after_create :send_notification - - scope :visible, lambda {|*args| - user = args.shift || User.current - - includes(:issue => :project). - where(Issue.visible_condition(user, *args)). - where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false) - } - - def save(*args) - # Do not save an empty journal - (details.empty? && notes.blank?) ? false : super - end - - # Returns journal details that are visible to user - def visible_details(user=User.current) - details.select do |detail| - if detail.property == 'cf' - detail.custom_field && detail.custom_field.visible_by?(project, user) - elsif detail.property == 'relation' - Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user) - else - true - end - end - end - - def each_notification(users, &block) - if users.any? - users_by_details_visibility = users.group_by do |user| - visible_details(user) - end - users_by_details_visibility.each do |visible_details, users| - if notes? || visible_details.any? - yield(users) - end - end - end - end - - # Returns the new status if the journal contains a status change, otherwise nil - def new_status - c = details.detect {|detail| detail.prop_key == 'status_id'} - (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil - end - - def new_value_for(prop) - c = details.detect {|detail| detail.prop_key == prop} - c ? c.value : nil - end - - def editable_by?(usr) - usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) - end - - def project - journalized.respond_to?(:project) ? journalized.project : nil - end - - def attachments - journalized.respond_to?(:attachments) ? journalized.attachments : nil - end - - # Returns a string of css classes - def css_classes - s = 'journal' - s << ' has-notes' unless notes.blank? - s << ' has-details' unless details.blank? - s << ' private-notes' if private_notes? - s - end - - def notify? - @notify != false - end - - def notify=(arg) - @notify = arg - end - - def notified_users - notified = journalized.notified_users - if private_notes? - notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} - end - notified - end - - def recipients - notified_users.map(&:mail) - end - - def notified_watchers - notified = journalized.notified_watchers - if private_notes? - notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} - end - notified - end - - def watcher_recipients - notified_watchers.map(&:mail) - end - - # Sets @custom_field instance variable on journals details using a single query - def self.preload_journals_details_custom_fields(journals) - field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq - if field_ids.any? - fields_by_id = CustomField.find_all_by_id(field_ids).inject({}) {|h, f| h[f.id] = f; h} - journals.each do |journal| - journal.details.each do |detail| - if detail.property == 'cf' - detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i] - end - end - end - end - journals - end - - private - - def split_private_notes - if private_notes? - if notes.present? - if details.any? - # Split the journal (notes/changes) so we don't have half-private journals - journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false) - journal.details = details - journal.save - self.details = [] - self.created_on = journal.created_on - end - else - # Blank notes should not be private - self.private_notes = false - end - end - true - end - - def send_notification - if notify? && (Setting.notified_events.include?('issue_updated') || - (Setting.notified_events.include?('issue_note_added') && notes.present?) || - (Setting.notified_events.include?('issue_status_updated') && new_status.present?) || - (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?) - ) - Mailer.deliver_issue_edit(self) - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6a/6ad28dd349d922a2ada017eb617dd963b5950896.svn-base --- a/.svn/pristine/6a/6ad28dd349d922a2ada017eb617dd963b5950896.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +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 SettingsControllerTest < ActionController::TestCase - fixtures :users - - def setup - 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' - 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_edit_commit_update_keywords - with_settings :commit_update_keywords => [ - {"keywords" => "fixes, resolves", "status_id" => "3"}, - {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} - ] do - get :edit - end - assert_response :success - assert_select 'tr.commit-keywords', 2 - assert_select 'tr.commit-keywords:nth-child(1)' do - assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'fixes, resolves' - assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do - assert_select 'option[value=3][selected=selected]' - end - end - assert_select 'tr.commit-keywords:nth-child(2)' do - assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'closes' - assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do - assert_select 'option[value=5][selected=selected]', :text => 'Closed' - end - assert_select 'select[name=?]', 'settings[commit_update_keywords][done_ratio][]' do - assert_select 'option[value=100][selected=selected]', :text => '100 %' - end - assert_select 'select[name=?]', 'settings[commit_update_keywords][if_tracker_id][]' do - assert_select 'option[value=2][selected=selected]', :text => 'Feature request' - end - end - end - - def test_edit_without_commit_update_keywords_should_show_blank_line - with_settings :commit_update_keywords => [] do - get :edit - end - assert_response :success - assert_select 'tr.commit-keywords', 1 do - assert_select 'input[name=?]:not([value])', 'settings[commit_update_keywords][keywords][]' - end - end - - def test_post_edit_commit_update_keywords - post :edit, :settings => { - :commit_update_keywords => { - :keywords => ["resolves", "closes"], - :status_id => ["3", "5"], - :done_ratio => ["", "100"], - :if_tracker_id => ["", "2"] - } - } - assert_redirected_to '/settings' - assert_equal([ - {"keywords" => "resolves", "status_id" => "3"}, - {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} - ], Setting.commit_update_keywords) - 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_get_non_configurable_plugin_settings - Redmine::Plugin.register(:foo) {} - - get :plugin, :id => 'foo' - assert_response 404 - - Redmine::Plugin.clear - end - - def test_post_plugin_settings - Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true) - Redmine::Plugin.register(:foo) do - settings :partial => 'not blank' # so that configurable? is true - end - - post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} - assert_redirected_to '/settings/plugin/foo' - end - - def test_post_non_configurable_plugin_settings - Redmine::Plugin.register(:foo) {} - - post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} - assert_response 404 - - Redmine::Plugin.clear - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6b/6b2c4a778dca89a31620a55a65371a0aa4017062.svn-base --- a/.svn/pristine/6b/6b2c4a778dca89a31620a55a65371a0aa4017062.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +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 RoutingBoardsTest < ActionController::IntegrationTest - def test_boards - assert_routing( - { :method => 'get', :path => "/projects/world_domination/boards" }, - { :controller => 'boards', :action => 'index', :project_id => 'world_domination' } - ) - assert_routing( - { :method => 'get', :path => "/projects/world_domination/boards/new" }, - { :controller => 'boards', :action => 'new', :project_id => 'world_domination' } - ) - assert_routing( - { :method => 'get', :path => "/projects/world_domination/boards/44" }, - { :controller => 'boards', :action => 'show', :project_id => 'world_domination', - :id => '44' } - ) - assert_routing( - { :method => 'get', :path => "/projects/world_domination/boards/44.atom" }, - { :controller => 'boards', :action => 'show', :project_id => 'world_domination', - :id => '44', :format => 'atom' } - ) - assert_routing( - { :method => 'get', :path => "/projects/world_domination/boards/44/edit" }, - { :controller => 'boards', :action => 'edit', :project_id => 'world_domination', - :id => '44' } - ) - assert_routing( - { :method => 'post', :path => "/projects/world_domination/boards" }, - { :controller => 'boards', :action => 'create', :project_id => 'world_domination' } - ) - assert_routing( - { :method => 'put', :path => "/projects/world_domination/boards/44" }, - { :controller => 'boards', :action => 'update', :project_id => 'world_domination', - :id => '44' } - ) - assert_routing( - { :method => 'delete', :path => "/projects/world_domination/boards/44" }, - { :controller => 'boards', :action => 'destroy', :project_id => 'world_domination', - :id => '44' } - ) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6b/6b42e6adb8523e15e9ff195fc3496e5284a69b00.svn-base --- a/.svn/pristine/6b/6b42e6adb8523e15e9ff195fc3496e5284a69b00.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +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 ActivitiesHelper - def sort_activity_events(events) - events_by_group = events.group_by(&:event_group) - sorted_events = [] - events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| - if group_events = events_by_group.delete(event.event_group) - group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| - sorted_events << [e, i > 0] - end - end - end - sorted_events - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6b/6b4c50390f939790cfd61f918f38b017e779b22e.svn-base --- a/.svn/pristine/6b/6b4c50390f939790cfd61f918f38b017e779b22e.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public 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 e248c7af89ec -r b450a9d58aed .svn/pristine/6c/6c32beaaa0775810013dacee7377be5689730569.svn-base --- a/.svn/pristine/6c/6c32beaaa0775810013dacee7377be5689730569.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,259 +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 - - include Redmine::I18n - - NUM_REV = 11 - - def setup - @project = Project.find(3) - @repository = Repository::Subversion.create(:project => @project, - :url => self.class.subversion_repository_url) - assert @repository - end - - def test_invalid_url - set_language_if_valid 'en' - ['invalid', 'http://', 'svn://', 'svn+ssh://', 'file://'].each do |url| - repo = Repository::Subversion.new( - :project => @project, - :identifier => 'test', - :url => url - ) - assert !repo.save - assert_equal ["is invalid"], repo.errors[:url] - end - end - - def test_valid_url - ['http://valid', 'svn://valid', 'svn+ssh://valid', 'file://valid'].each do |url| - repo = Repository::Subversion.new( - :project => @project, - :identifier => 'test', - :url => url - ) - assert repo.save - assert_equal [], repo.errors[:url] - assert repo.destroy - end - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/6d/6d3df7e3002585d4b82d0225bf30bfb49566483a.svn-base --- a/.svn/pristine/6d/6d3df7e3002585d4b82d0225bf30bfb49566483a.svn-base Mon Mar 17 08:54:02 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-2013 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module NewsHelper -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6d/6ddb785f30a0d1233af9e71a8c3b041341c78af5.svn-base --- a/.svn/pristine/6d/6ddb785f30a0d1233af9e71a8c3b041341c78af5.svn-base Mon Mar 17 08:54:02 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. - -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 e248c7af89ec -r b450a9d58aed .svn/pristine/6e/6e61a701e2749bf002d37820f0ff37fbc966f8bd.svn-base --- a/.svn/pristine/6e/6e61a701e2749bf002d37820f0ff37fbc966f8bd.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +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: - module CoreExtensions #:nodoc: - module String #:nodoc: - # Custom string inflections - module Inflections - def with_leading_slash - starts_with?('/') ? self : "/#{ self }" - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/6f/6f3de0785368c550e0511d7f874b1261b16e18b8.svn-base --- a/.svn/pristine/6f/6f3de0785368c550e0511d7f874b1261b16e18b8.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +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 IssuesTest < ActionController::IntegrationTest - fixtures :projects, - :users, - :roles, - :members, - :member_roles, - :trackers, - :projects_trackers, - :enabled_modules, - :issue_statuses, - :issues, - :enumerations, - :custom_fields, - :custom_values, - :custom_fields_trackers - - # create an issue - def test_add_issue - log_user('jsmith', 'jsmith') - get 'projects/1/issues/new', :tracker_id => '1' - assert_response :success - assert_template 'issues/new' - - post 'projects/1/issues', :tracker_id => "1", - :issue => { :start_date => "2006-12-26", - :priority_id => "4", - :subject => "new test issue", - :category_id => "", - :description => "new issue", - :done_ratio => "0", - :due_date => "", - :assigned_to_id => "" }, - :custom_fields => {'2' => 'Value for field 2'} - # find created issue - issue = Issue.find_by_subject("new test issue") - assert_kind_of Issue, issue - - # check redirection - assert_redirected_to :controller => 'issues', :action => 'show', :id => issue - follow_redirect! - assert_equal issue, assigns(:issue) - - # check issue attributes - assert_equal 'jsmith', issue.author.login - assert_equal 1, issue.project.id - assert_equal 1, issue.status.id - end - - # add then remove 2 attachments to an issue - def test_issue_attachments - log_user('jsmith', 'jsmith') - set_tmp_attachments_directory - - put 'issues/1', - :notes => 'Some notes', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}} - assert_redirected_to "/issues/1" - - # make sure attachment was saved - attachment = Issue.find(1).attachments.find_by_filename("testfile.txt") - assert_kind_of Attachment, attachment - assert_equal Issue.find(1), attachment.container - assert_equal 'This is an attachment', attachment.description - # verify the size of the attachment stored in db - #assert_equal file_data_1.length, attachment.filesize - # verify that the attachment was written to disk - assert File.exist?(attachment.diskfile) - - # remove the attachments - Issue.find(1).attachments.each(&:destroy) - assert_equal 0, Issue.find(1).attachments.length - end - - def test_other_formats_links_on_index - get '/projects/ecookbook/issues' - - %w(Atom PDF CSV).each do |format| - assert_tag :a, :content => format, - :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", - :rel => 'nofollow' } - end - end - - def test_other_formats_links_on_index_without_project_id_in_url - get '/issues', :project_id => 'ecookbook' - - %w(Atom PDF CSV).each do |format| - assert_tag :a, :content => format, - :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", - :rel => 'nofollow' } - end - end - - def test_pagination_links_on_index - Setting.per_page_options = '2' - get '/projects/ecookbook/issues' - - assert_tag :a, :content => '2', - :attributes => { :href => '/projects/ecookbook/issues?page=2' } - - end - - def test_pagination_links_on_index_without_project_id_in_url - Setting.per_page_options = '2' - get '/issues', :project_id => 'ecookbook' - - assert_tag :a, :content => '2', - :attributes => { :href => '/projects/ecookbook/issues?page=2' } - - end - - def test_issue_with_user_custom_field - @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) - Role.anonymous.add_permission! :add_issues, :edit_issues - users = Project.find(1).users - tester = users.first - - # Issue form - get '/projects/ecookbook/issues/new' - assert_response :success - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, - :children => {:count => (users.size + 1)}, # +1 for blank value - :child => { - :tag => 'option', - :attributes => {:value => tester.id.to_s}, - :content => tester.name - } - - # Create issue - assert_difference 'Issue.count' do - post '/projects/ecookbook/issues', - :issue => { - :tracker_id => '1', - :priority_id => '4', - :subject => 'Issue with user custom field', - :custom_field_values => {@field.id.to_s => users.first.id.to_s} - } - end - issue = Issue.first(:order => 'id DESC') - assert_response 302 - - # Issue view - follow_redirect! - assert_tag :th, - :content => /Tester/, - :sibling => { - :tag => 'td', - :content => tester.name - } - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, - :children => {:count => (users.size + 1)}, # +1 for blank value - :child => { - :tag => 'option', - :attributes => {:value => tester.id.to_s, :selected => 'selected'}, - :content => tester.name - } - - # Update issue - new_tester = users[1] - assert_difference 'Journal.count' do - put "/issues/#{issue.id}", - :notes => 'Updating custom field', - :issue => { - :custom_field_values => {@field.id.to_s => new_tester.id.to_s} - } - end - assert_response 302 - - # Issue view - follow_redirect! - assert_tag :content => 'Tester', - :ancestor => {:tag => 'ul', :attributes => {:class => /details/}}, - :sibling => { - :content => tester.name, - :sibling => { - :content => new_tester.name - } - } - end - - def test_update_using_invalid_http_verbs - subject = 'Updated by an invalid http verb' - - get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith') - assert_response 404 - assert_not_equal subject, Issue.find(1).subject - - post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith') - assert_response 404 - assert_not_equal subject, Issue.find(1).subject - end - - def test_get_watch_should_be_invalid - assert_no_difference 'Watcher.count' do - get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith') - assert_response 404 - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/70/7005c1ac60bfaef4c1c73b8db2692b1b9a732a71.svn-base --- a/.svn/pristine/70/7005c1ac60bfaef4c1c73b8db2692b1b9a732a71.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/70/70c517dee606cfef8e0ccb2978ba7929048caf58.svn-base --- a/.svn/pristine/70/70c517dee606cfef8e0ccb2978ba7929048caf58.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +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 WatchersHelper - - def watcher_tag(object, user, options={}) - ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." - watcher_link(object, user) - end - - def watcher_link(objects, user) - return '' unless user && user.logged? - objects = Array.wrap(objects) - - watched = Watcher.any_watched?(objects, user) - css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') - text = watched ? l(:button_unwatch) : l(:button_watch) - url = watch_path( - :object_type => objects.first.class.to_s.underscore, - :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) - ) - method = watched ? 'delete' : 'post' - - link_to text, url, :remote => true, :method => method, :class => css - end - - # Returns the css class used to identify watch links for a given +object+ - def watcher_css(objects) - objects = Array.wrap(objects) - id = (objects.size == 1 ? objects.first.id : 'bulk') - "#{objects.first.class.to_s.underscore}-#{id}-watcher" - end - - # Returns a comma separated list of users watching the given object - def watchers_list(object) - remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) - content = ''.html_safe - lis = object.watcher_users.collect do |user| - s = ''.html_safe - s << avatar(user, :size => "16").to_s - s << link_to_user(user, :class => 'user') - if remove_allowed - url = {:controller => 'watchers', - :action => 'destroy', - :object_type => object.class.to_s.underscore, - :object_id => object.id, - :user_id => user} - s << ' ' - s << link_to(image_tag('delete.png'), url, - :remote => true, :method => 'delete', :class => "delete") - end - content << content_tag('li', s, :class => "user-#{user.id}") - end - content.present? ? content_tag('ul', content, :class => 'watchers') : content - end - - def watchers_checkboxes(object, users, checked=nil) - users.map do |user| - c = checked.nil? ? object.watched_by?(user) : checked - tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil - content_tag 'label', "#{tag} #{h(user)}".html_safe, - :id => "issue_watcher_user_ids_#{user.id}", - :class => "floating" - end.join.html_safe - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/70/70e864184fd8408391ed37d61337591c474549c4.svn-base --- a/.svn/pristine/70/70e864184fd8408391ed37d61337591c474549c4.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +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 Macros - module Definitions - # Returns true if +name+ is the name of an existing macro - def macro_exists?(name) - Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym) - end - - def exec_macro(name, obj, args, text) - macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym] - return unless macro_options - - method_name = "macro_#{name}" - unless macro_options[:parse_args] == false - args = args.split(',').map(&:strip) - end - - begin - if self.class.instance_method(method_name).arity == 3 - send(method_name, obj, args, text) - elsif text - raise "This macro does not accept a block of text" - else - send(method_name, obj, args) - end - rescue => e - "
    Error executing the #{h name} macro (#{h e.to_s})
    ".html_safe - end - end - - def extract_macro_options(args, *keys) - options = {} - while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym) - options[$1.downcase.to_sym] = $2 - args.pop - end - return [args, options] - end - end - - @@available_macros = {} - mattr_accessor :available_macros - - class << self - # Plugins can use this method to define new macros: - # - # Redmine::WikiFormatting::Macros.register do - # desc "This is my macro" - # macro :my_macro do |obj, args| - # "My macro output" - # end - # - # desc "This is my macro that accepts a block of text" - # macro :my_macro do |obj, args, text| - # "My macro output" - # end - # end - def register(&block) - class_eval(&block) if block_given? - end - - # Defines a new macro with the given name, options and block. - # - # Options: - # * :desc - A description of the macro - # * :parse_args => false - Disables arguments parsing (the whole arguments - # string is passed to the macro) - # - # Macro blocks accept 2 or 3 arguments: - # * obj: the object that is rendered (eg. an Issue, a WikiContent...) - # * args: macro arguments - # * text: the block of text given to the macro (should be present only if the - # macro accepts a block of text). text is a String or nil if the macro is - # invoked without a block of text. - # - # Examples: - # By default, when the macro is invoked, the coma separated list of arguments - # is split and passed to the macro block as an array. If no argument is given - # the macro will be invoked with an empty array: - # - # macro :my_macro do |obj, args| - # # args is an array - # # and this macro do not accept a block of text - # end - # - # You can disable arguments spliting with the :parse_args => false option. In - # this case, the full string of arguments is passed to the macro: - # - # macro :my_macro, :parse_args => false do |obj, args| - # # args is a string - # end - # - # Macro can optionally accept a block of text: - # - # macro :my_macro do |obj, args, text| - # # this macro accepts a block of text - # end - # - # Macros are invoked in formatted text using double curly brackets. Arguments - # must be enclosed in parenthesis if any. A new line after the macro name or the - # arguments starts the block of text that will be passe to the macro (invoking - # a macro that do not accept a block of text with some text will fail). - # Examples: - # - # No arguments: - # {{my_macro}} - # - # With arguments: - # {{my_macro(arg1, arg2)}} - # - # With a block of text: - # {{my_macro - # multiple lines - # of text - # }} - # - # With arguments and a block of text - # {{my_macro(arg1, arg2) - # multiple lines - # of text - # }} - # - # If a block of text is given, the closing tag }} must be at the start of a new line. - def macro(name, options={}, &block) - options.assert_valid_keys(:desc, :parse_args) - unless name.to_s.match(/\A\w+\z/) - raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)" - end - unless block_given? - raise "Can not create a macro without a block!" - end - name = name.to_s.downcase.to_sym - available_macros[name] = {:desc => @@desc || ''}.merge(options) - @@desc = nil - Definitions.send :define_method, "macro_#{name}", &block - end - - # Sets description for the next macro to be defined - def desc(txt) - @@desc = txt - end - end - - # Builtin macros - desc "Sample macro." - macro :hello_world do |obj, args, text| - h("Hello world! Object: #{obj.class.name}, " + - (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") + - " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.") - ) - end - - desc "Displays a list of all available macros, including description if available." - macro :macro_list do |obj, args| - out = ''.html_safe - @@available_macros.each do |macro, options| - out << content_tag('dt', content_tag('code', macro.to_s)) - out << content_tag('dd', textilizable(options[:desc])) - end - content_tag('dl', out) - end - - desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + - " !{{child_pages}} -- can be used from a wiki page only\n" + - " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n" - " !{{child_pages(Foo)}} -- lists all children of page Foo\n" + - " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo" - macro :child_pages do |obj, args| - args, options = extract_macro_options(args, :parent, :depth) - options[:depth] = options[:depth].to_i if options[:depth].present? - - page = nil - if args.size > 0 - page = Wiki.find_page(args.first.to_s, :project => @project) - elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) - page = obj.page - else - raise 'With no argument, this macro can be called from wiki pages only.' - end - raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) - pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id) - render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) - end - - desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" - macro :include do |obj, args| - page = Wiki.find_page(args.first.to_s, :project => @project) - raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) - @included_wiki_pages ||= [] - raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) - @included_wiki_pages << page.title - out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false) - @included_wiki_pages.pop - out - end - - desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}" - macro :collapse do |obj, args, text| - html_id = "collapse-#{Redmine::Utils.random_hex(4)}" - show_label = args[0] || l(:button_show) - hide_label = args[1] || args[0] || l(:button_hide) - js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);" - out = ''.html_safe - out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed') - out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;') - out << content_tag('div', textilizable(text, :object => obj), :id => html_id, :class => 'collapsed-text', :style => 'display:none;') - out - end - - desc "Displays a clickable thumbnail of an attached image. Examples:\n\n
    {{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}
    " - macro :thumbnail do |obj, args| - args, options = extract_macro_options(args, :size, :title) - filename = args.first - raise 'Filename required' unless filename.present? - size = options[:size] - raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/) - size = size.to_i - size = nil unless size > 0 - if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename) - title = options[:title] || attachment.title - img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename) - link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title) - else - raise "Attachment #{filename} not found" - end - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/71/714859f726ef05e105f7c83270e22457a9138242.svn-base --- a/.svn/pristine/71/714859f726ef05e105f7c83270e22457a9138242.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +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: - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/72/72607bd4ab6cb744483458ba6f765737733a7ddf.svn-base --- a/.svn/pristine/72/72607bd4ab6cb744483458ba6f765737733a7ddf.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +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 'action_view/helpers/form_helper' - -class Redmine::Views::LabelledFormBuilder < ActionView::Helpers::FormBuilder - include Redmine::I18n - - (field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + - %w(date_select)).each do |selector| - src = <<-END_SRC - def #{selector}(field, options = {}) - label_for_field(field, options) + super(field, options.except(:label)).html_safe - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - - def select(field, choices, options = {}, html_options = {}) - label_for_field(field, options) + super(field, choices, options, html_options.except(:label)).html_safe - end - - def time_zone_select(field, priority_zones = nil, options = {}, html_options = {}) - label_for_field(field, options) + super(field, priority_zones, options, html_options.except(:label)).html_safe - end - - # Returns a label tag for the given field - def label_for_field(field, options = {}) - return ''.html_safe if options.delete(:no_label) - text = options[:label].is_a?(Symbol) ? l(options[:label]) : options[:label] - text ||= l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) - text += @template.content_tag("span", " *", :class => "required") if options.delete(:required) - @template.content_tag("label", text.html_safe, - :class => (@object && @object.errors[field].present? ? "error" : nil), - :for => (@object_name.to_s + "_" + field.to_s)) - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/73/7309716b5db2ba64867602eae0ca0931c23991c4.svn-base --- a/.svn/pristine/73/7309716b5db2ba64867602eae0ca0931c23991c4.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +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/setup' - - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/73/7324b60def8b61259e4fe88d626f6df74b027500.svn-base --- a/.svn/pristine/73/7324b60def8b61259e4fe88d626f6df74b027500.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,468 +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 '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 - - self.use_transactional_fixtures = true - self.use_instantiated_fixtures = false - - 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({}) do |h, k| - h[k] = case Setting[k] - when Symbol, false, true, nil - Setting[k] - else - Setting[k].dup - end - h - end - options.each {|k, v| Setting[k] = v} - yield - ensure - 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.where(:login => login).first - 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, message=nil) - assert !s.include?(expected), (message || "\"#{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, message=nil) - if expected.is_a?(String) - assert_include expected, mail_body(mail), message - else - assert_match expected, mail_body(mail), message - end - end - - def assert_mail_body_no_match(expected, mail, message=nil) - if expected.is_a?(String) - assert_not_include expected, mail_body(mail), message - else - assert_no_match expected, mail_body(mail), message - end - end - - def mail_body(mail) - mail.parts.first.body.encoded - end -end - -module Redmine - module ApiTest - # Base class for API tests - class Base < ActionDispatch::IntegrationTest - # Test that a request allows the three types of API authentication - # - # * HTTP Basic with username and password - # * HTTP Basic with an api key for the username - # * Key based with the key=X parameter - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) - should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) - should_allow_http_basic_auth_with_key(http_method, url, parameters, options) - should_allow_key_based_auth(http_method, url, parameters, options) - end - - # Test that a request allows the username and password for HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth using a username and password for #{http_method} #{url}" do - context "with a valid HTTP authentication" do - setup do - @user = User.generate! do |user| - user.admin = true - user.password = 'my_password' - end - send(http_method, url, parameters, credentials(@user.login, 'my_password')) - end - - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - 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 - end -end - -# URL helpers do not work with config.threadsafe! -# https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454 -ActionView::TestCase::TestController.instance_eval do - helper Rails.application.routes.url_helpers -end -ActionView::TestCase::TestController.class_eval do - def _routes - Rails.application.routes - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/73/73cf79b8d745cac74b4b29d58fecb04843a6f726.svn-base --- a/.svn/pristine/73/73cf79b8d745cac74b4b29d58fecb04843a6f726.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,245 +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 VersionTest < ActiveSupport::TestCase - fixtures :projects, :users, :issues, :issue_statuses, :trackers, - :enumerations, :versions, :projects_trackers - - def test_create - v = Version.new(:project => Project.find(1), :name => '1.1', - :effective_date => '2011-03-25') - assert v.save - assert_equal 'open', v.status - assert_equal 'none', v.sharing - end - - def test_invalid_effective_date_validation - v = Version.new(:project => Project.find(1), :name => '1.1', - :effective_date => '99999-01-01') - assert !v.valid? - v.effective_date = '2012-11-33' - assert !v.valid? - v.effective_date = '2012-31-11' - assert !v.valid? - v.effective_date = '-2012-31-11' - assert !v.valid? - v.effective_date = 'ABC' - assert !v.valid? - assert_include I18n.translate('activerecord.errors.messages.not_a_date'), - v.errors[:effective_date] - end - - def test_progress_should_be_0_with_no_assigned_issues - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - assert_equal 0, v.completed_percent - assert_equal 0, v.closed_percent - end - - def test_progress_should_be_0_with_unbegun_assigned_issues - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v) - add_issue(v, :done_ratio => 0) - assert_progress_equal 0, v.completed_percent - assert_progress_equal 0, v.closed_percent - end - - def test_progress_should_be_100_with_closed_assigned_issues - project = Project.find(1) - status = IssueStatus.where(:is_closed => true).first - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v, :status => status) - add_issue(v, :status => status, :done_ratio => 20) - add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25) - add_issue(v, :status => status, :estimated_hours => 15) - assert_progress_equal 100.0, v.completed_percent - assert_progress_equal 100.0, v.closed_percent - end - - def test_progress_should_consider_done_ratio_of_open_assigned_issues - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v) - add_issue(v, :done_ratio => 20) - add_issue(v, :done_ratio => 70) - assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent - assert_progress_equal 0, v.closed_percent - end - - def test_progress_should_consider_closed_issues_as_completed - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v) - add_issue(v, :done_ratio => 20) - add_issue(v, :status => IssueStatus.where(:is_closed => true).first) - assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent - assert_progress_equal (100.0)/3, v.closed_percent - end - - def test_progress_should_consider_estimated_hours_to_weigth_issues - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v, :estimated_hours => 10) - add_issue(v, :estimated_hours => 20, :done_ratio => 30) - add_issue(v, :estimated_hours => 40, :done_ratio => 10) - add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first) - assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent - assert_progress_equal 25.0/95.0*100, v.closed_percent - end - - def test_progress_should_consider_average_estimated_hours_to_weigth_unestimated_issues - project = Project.find(1) - v = Version.create!(:project => project, :name => 'Progress') - add_issue(v, :done_ratio => 20) - add_issue(v, :status => IssueStatus.where(:is_closed => true).first) - add_issue(v, :estimated_hours => 10, :done_ratio => 30) - add_issue(v, :estimated_hours => 40, :done_ratio => 10) - assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent - assert_progress_equal 25.0/100.0*100, v.closed_percent - end - - def test_should_sort_scheduled_then_unscheduled_versions - Version.delete_all - v4 = Version.create!(:project_id => 1, :name => 'v4') - v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14') - v2 = Version.create!(:project_id => 1, :name => 'v1') - v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02') - v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02') - - assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort - assert_equal [v5, v3, v1, v2, v4], Version.sorted.all - end - - def test_completed_should_be_false_when_due_today - version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today') - assert_equal false, version.completed? - end - - test "#behind_schedule? should be false if there are no issues assigned" do - version = Version.generate!(:effective_date => Date.yesterday) - assert_equal false, version.behind_schedule? - end - - test "#behind_schedule? should be false if there is no effective_date" do - version = Version.generate!(:effective_date => nil) - assert_equal false, version.behind_schedule? - end - - test "#behind_schedule? should be false if all of the issues are ahead of schedule" do - version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) - add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - assert_equal 60, version.completed_percent - assert_equal false, version.behind_schedule? - end - - test "#behind_schedule? should be true if any of the issues are behind schedule" do - version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) - add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left - add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left - assert_equal 40, version.completed_percent - assert_equal true, version.behind_schedule? - end - - test "#behind_schedule? should be false if all of the issues are complete" do - version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date) - add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span - add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span - assert_equal 100, version.completed_percent - assert_equal false, version.behind_schedule? - end - - test "#estimated_hours should return 0 with no assigned issues" do - version = Version.generate! - assert_equal 0, version.estimated_hours - end - - test "#estimated_hours should return 0 with no estimated hours" do - version = Version.create!(:project_id => 1, :name => 'test') - add_issue(version) - assert_equal 0, version.estimated_hours - end - - test "#estimated_hours should return return the sum of estimated hours" do - version = Version.create!(:project_id => 1, :name => 'test') - add_issue(version, :estimated_hours => 2.5) - add_issue(version, :estimated_hours => 5) - assert_equal 7.5, version.estimated_hours - end - - test "#estimated_hours should return the sum of leaves estimated hours" do - version = Version.create!(:project_id => 1, :name => 'test') - parent = add_issue(version) - add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id) - add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id) - assert_equal 7.5, version.estimated_hours - end - - test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do - User.current = User.find(1) # Need the admin's permissions - - @version = Version.find(7) - # Separate hierarchy - project_1_issue = Issue.find(1) - project_1_issue.fixed_version = @version - assert project_1_issue.save, project_1_issue.errors.full_messages.to_s - - project_5_issue = Issue.find(6) - project_5_issue.fixed_version = @version - assert project_5_issue.save - - # Project - project_2_issue = Issue.find(4) - project_2_issue.fixed_version = @version - assert project_2_issue.save - - # Update the sharing - @version.sharing = 'none' - assert @version.save - - # Project 1 now out of the shared scope - project_1_issue.reload - assert_equal nil, project_1_issue.fixed_version, - "Fixed version is still set after changing the Version's sharing" - - # Project 5 now out of the shared scope - project_5_issue.reload - assert_equal nil, project_5_issue.fixed_version, - "Fixed version is still set after changing the Version's sharing" - - # Project 2 issue remains - project_2_issue.reload - assert_equal @version, project_2_issue.fixed_version - end - - private - - def add_issue(version, attributes={}) - Issue.create!({:project => version.project, - :fixed_version => version, - :subject => 'Test', - :author => User.first, - :tracker => version.project.trackers.first}.merge(attributes)) - end - - def assert_progress_equal(expected_float, actual_float, message="") - assert_in_delta(expected_float, actual_float, 0.000001, message="") - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/74/74bcc529787db769558f2ce1cd97484fa4286471.svn-base --- a/.svn/pristine/74/74bcc529787db769558f2ce1cd97484fa4286471.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +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 QueriesController < ApplicationController - menu_item :issues - before_filter :find_query, :except => [:new, :create, :index] - before_filter :find_optional_project, :only => [:new, :create] - - accept_api_auth :index - - include QueriesHelper - - def index - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = per_page_option - end - - @query_count = IssueQuery.visible.count - @query_pages = Paginator.new @query_count, @limit, params['page'] - @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") - - respond_to do |format| - format.api - end - end - - def new - @query = IssueQuery.new - @query.user = User.current - @query.project = @project - @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - @query.build_from_params(params) - end - - def create - @query = IssueQuery.new(params[:query]) - @query.user = User.current - @query.project = params[:query_is_for_all] ? nil : @project - @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - @query.build_from_params(params) - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_create) - redirect_to_issues(:query_id => @query) - else - render :action => 'new', :layout => !request.xhr? - end - end - - def edit - end - - def update - @query.attributes = params[:query] - @query.project = nil if params[:query_is_for_all] - @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - @query.build_from_params(params) - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_update) - redirect_to_issues(:query_id => @query) - else - render :action => 'edit' - end - end - - def destroy - @query.destroy - redirect_to_issues(:set_filter => 1) - end - -private - def find_query - @query = IssueQuery.find(params[:id]) - @project = @query.project - render_403 unless @query.editable_by?(User.current) - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_optional_project - @project = Project.find(params[:project_id]) if params[:project_id] - render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true) - rescue ActiveRecord::RecordNotFound - render_404 - end - - def redirect_to_issues(options) - if params[:gantt] - if @project - redirect_to project_gantt_path(@project, options) - else - redirect_to issues_gantt_path(options) - end - else - redirect_to _project_issues_path(@project, options) - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/75/75cbf5c4f829621de0ddf156af2f88bad5bed475.svn-base --- a/.svn/pristine/75/75cbf5c4f829621de0ddf156af2f88bad5bed475.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +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')")) - assert_select "div#sidebar" do - # Links to versions anchors - assert_select 'a[href=?]', '#2.0' - # Links to completed versions in the sidebar - assert_select 'a[href=?]', '/versions/1' - end - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/76/760034b00a77f0a73cc21094aabb292a6e6e29ae.svn-base --- a/.svn/pristine/76/760034b00a77f0a73cc21094aabb292a6e6e29ae.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,869 +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_invalid_project_should_be_assigned_to_default_project - issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email| - email.gsub!(/^Project:.+$/, 'Project: invalid') - end - assert issue.is_a?(Issue) - assert !issue.new_record? - assert_equal 'ecookbook', issue.project.identifier - 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_multiple_inline_text_parts_should_be_appended_to_issue_description - issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'}) - assert_include 'first', issue.description - assert_include 'second', issue.description - assert_include 'third', issue.description - end - - def test_attachment_text_part_should_be_added_as_issue_attachment - issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'}) - assert_not_include 'Plain text attachment', issue.description - attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'} - assert_not_nil attachment - assert_include 'Plain text attachment', File.read(attachment.diskfile) - 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_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored - with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do - issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) - assert issue.is_a?(Issue) - assert !issue.new_record? - assert_equal 0, issue.reload.attachments.size - end - end - - def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached - with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do - issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) - assert issue.is_a?(Issue) - assert !issue.new_record? - assert_equal 1, issue.reload.attachments.size - 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_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 - - def test_extract_options_from_env_should_return_options - options = MailHandler.extract_options_from_env({ - 'tracker' => 'defect', - 'project' => 'foo', - 'unknown_user' => 'create' - }) - - assert_equal({ - :issue => {:tracker => 'defect', :project => 'foo'}, - :unknown_user => 'create' - }, options) - 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 e248c7af89ec -r b450a9d58aed .svn/pristine/76/7613b919071a0b15c5315d48513bd037d7d92c4b.svn-base --- a/.svn/pristine/76/7613b919071a0b15c5315d48513bd037d7d92c4b.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +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 compute the start and end dates of a calendar - class Calendar - include Redmine::I18n - attr_reader :startdt, :enddt - - def initialize(date, lang = current_language, period = :month) - @date = date - @events = [] - @ending_events_by_days = {} - @starting_events_by_days = {} - set_language_if_valid lang - case period - when :month - @startdt = Date.civil(date.year, date.month, 1) - @enddt = (@startdt >> 1)-1 - # starts from the first day of the week - @startdt = @startdt - (@startdt.cwday - first_wday)%7 - # ends on the last day of the week - @enddt = @enddt + (last_wday - @enddt.cwday)%7 - when :week - @startdt = date - (date.cwday - first_wday)%7 - @enddt = date + (last_wday - date.cwday)%7 - else - raise 'Invalid period' - end - end - - # Sets calendar events - def events=(events) - @events = events - @ending_events_by_days = @events.group_by {|event| event.due_date} - @starting_events_by_days = @events.group_by {|event| event.start_date} - end - - # Returns events for the given day - def events_on(day) - ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq - end - - # Calendar current month - def month - @date.month - end - - # Return the first day of week - # 1 = Monday ... 7 = Sunday - def first_wday - case Setting.start_of_week.to_i - when 1 - @first_dow ||= (1 - 1)%7 + 1 - when 6 - @first_dow ||= (6 - 1)%7 + 1 - when 7 - @first_dow ||= (7 - 1)%7 + 1 - else - @first_dow ||= (l(:general_first_day_of_week).to_i - 1)%7 + 1 - end - end - - def last_wday - @last_dow ||= (first_wday + 5)%7 + 1 - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/76/76c6293fe37375af70e9b0a4cdcbec2106159b62.svn-base --- a/.svn/pristine/76/76c6293fe37375af70e9b0a4cdcbec2106159b62.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,607 +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 ProjectsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, - :attachments, :custom_fields, :custom_values, :time_entries - - def setup - @request.session[:user_id] = nil - Setting.default_language = 'en' - end - - def test_index_by_anonymous_should_not_show_private_projects - get :index - assert_response :success - assert_template 'index' - projects = assigns(:projects) - assert_not_nil projects - assert projects.all?(&:is_public?) - - assert_select 'ul' do - assert_select 'li' do - assert_select 'a', :text => 'eCookbook' - assert_select 'ul' do - assert_select 'a', :text => 'Child of private child' - end - end - end - assert_select 'a', :text => /Private child of eCookbook/, :count => 0 - end - - def test_index_atom - get :index, :format => 'atom' - assert_response :success - assert_template 'common/feed' - assert_select 'feed>title', :text => 'Redmine: Latest projects' - assert_select 'feed>entry', :count => Project.visible(User.current).count - end - - test "#index by non-admin user with view_time_entries permission should show overall spent time link" do - @request.session[:user_id] = 3 - get :index - assert_template 'index' - assert_select 'a[href=?]', '/time_entries' - end - - test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do - Role.find(2).remove_permission! :view_time_entries - Role.non_member.remove_permission! :view_time_entries - Role.anonymous.remove_permission! :view_time_entries - @request.session[:user_id] = 3 - - get :index - assert_template 'index' - assert_select 'a[href=?]', '/time_entries', 0 - end - - test "#new by admin user should accept get" do - @request.session[:user_id] = 1 - - get :new - assert_response :success - assert_template 'new' - end - - test "#new by non-admin user with add_project permission should accept get" do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - - get :new - assert_response :success - assert_template 'new' - assert_select 'select[name=?]', 'project[parent_id]', 0 - end - - test "#new by non-admin user with add_subprojects permission should accept get" do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - - get :new, :parent_id => 'ecookbook' - assert_response :success - assert_template 'new' - - assert_select 'select[name=?]', 'project[parent_id]' do - # parent project selected - assert_select 'option[value=1][selected=selected]' - # no empty value - assert_select 'option[value=]', 0 - end - end - - test "#create by admin user should create a new project" do - @request.session[:user_id] = 1 - - post :create, - :project => { - :name => "blog", - :description => "weblog", - :homepage => 'http://weblog', - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - # an issue custom field that is not for all project - :issue_custom_field_ids => ['9'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert project.active? - assert_equal 'weblog', project.description - assert_equal 'http://weblog', project.homepage - assert_equal true, project.is_public? - assert_nil project.parent - assert_equal 'Beta', project.custom_value_for(3).value - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - assert project.issue_custom_fields.include?(IssueCustomField.find(9)) - end - - test "#create by admin user should create a new subproject" do - @request.session[:user_id] = 1 - - assert_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - end - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal Project.find(1), project.parent - end - - test "#create by admin user should continue" do - @request.session[:user_id] = 1 - - assert_difference 'Project.count' do - post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' - end - assert_redirected_to '/projects/new' - end - - test "#create by non-admin user with add_project permission should create a new project" do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal 'weblog', project.description - assert_equal true, project.is_public? - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - - # User should be added as a project member - assert User.find(9).member_of?(project) - assert_equal 1, project.members.size - end - - test "#create by non-admin user with add_project permission should fail with parent_id" do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_equal [], project.errors[:parent_id] - end - - test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - project = Project.find_by_name('blog') - end - - test "#create by non-admin user with add_subprojects permission should fail without parent_id" do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' } - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_equal [], project.errors[:parent_id] - end - - test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - - assert !User.find(2).member_of?(Project.find(6)) - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 6 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_equal [], project.errors[:parent_id] - end - - def test_create_subproject_with_inherit_members_should_inherit_members - Role.find_by_name('Manager').add_permission! :add_subprojects - parent = Project.find(1) - @request.session[:user_id] = 2 - - assert_difference 'Project.count' do - post :create, :project => { - :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1' - } - assert_response 302 - end - - project = Project.order('id desc').first - assert_equal 'inherited', project.name - assert_equal parent, project.parent - assert project.memberships.count > 0 - assert_equal parent.memberships.count, project.memberships.count - end - - def test_create_should_preserve_modules_on_validation_failure - with_settings :default_projects_modules => ['issue_tracking', 'repository'] do - @request.session[:user_id] = 1 - assert_no_difference 'Project.count' do - post :create, :project => { - :name => "blog", - :identifier => "", - :enabled_module_names => %w(issue_tracking news) - } - end - assert_response :success - project = assigns(:project) - assert_equal %w(issue_tracking news), project.enabled_module_names.sort - end - end - - def test_show_by_id - get :show, :id => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - end - - def test_show_by_identifier - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - - assert_select 'li', :text => /Development status/ - end - - def test_show_should_not_display_empty_sidebar - p = Project.find(1) - p.enabled_module_names = [] - p.save! - - get :show, :id => 'ecookbook' - assert_response :success - assert_select '#main.nosidebar' - end - - def test_show_should_not_display_hidden_custom_fields - ProjectCustomField.find_by_name('Development status').update_attribute :visible, false - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - - assert_select 'li', :text => /Development status/, :count => 0 - end - - def test_show_should_not_fail_when_custom_values_are_nil - project = Project.find_by_identifier('ecookbook') - project.custom_values.first.update_attribute(:value, nil) - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - end - - def show_archived_project_should_be_denied - project = Project.find_by_identifier('ecookbook') - project.archive! - - get :show, :id => 'ecookbook' - assert_response 403 - assert_nil assigns(:project) - assert_select 'p', :text => /archived/ - end - - def test_show_should_not_show_private_subprojects_that_are_not_visible - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_select 'a', :text => /Private child/, :count => 0 - end - - def test_show_should_show_private_subprojects_that_are_visible - @request.session[:user_id] = 2 # manager who is a member of the private subproject - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_select 'a', :text => /Private child/ - end - - def test_settings - @request.session[:user_id] = 2 # manager - get :settings, :id => 1 - assert_response :success - assert_template 'settings' - end - - def test_settings_of_subproject - @request.session[:user_id] = 2 - get :settings, :id => 'private-child' - assert_response :success - assert_template 'settings' - - assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]' - end - - def test_settings_should_be_denied_for_member_on_closed_project - Project.find(1).close - @request.session[:user_id] = 2 # manager - - get :settings, :id => 1 - assert_response 403 - end - - def test_settings_should_be_denied_for_anonymous_on_closed_project - Project.find(1).close - - get :settings, :id => 1 - assert_response 302 - end - - def test_update - @request.session[:user_id] = 2 # manager - post :update, :id => 1, :project => {:name => 'Test changed name', - :issue_custom_field_ids => ['']} - assert_redirected_to '/projects/ecookbook/settings' - project = Project.find(1) - assert_equal 'Test changed name', project.name - end - - def test_update_with_failure - @request.session[:user_id] = 2 # manager - post :update, :id => 1, :project => {:name => ''} - assert_response :success - assert_template 'settings' - assert_error_tag :content => /name can't be blank/i - end - - def test_update_should_be_denied_for_member_on_closed_project - Project.find(1).close - @request.session[:user_id] = 2 # manager - - post :update, :id => 1, :project => {:name => 'Closed'} - assert_response 403 - assert_equal 'eCookbook', Project.find(1).name - end - - def test_update_should_be_denied_for_anonymous_on_closed_project - Project.find(1).close - - post :update, :id => 1, :project => {:name => 'Closed'} - assert_response 302 - assert_equal 'eCookbook', Project.find(1).name - end - - def test_modules - @request.session[:user_id] = 2 - Project.find(1).enabled_module_names = ['issue_tracking', 'news'] - - post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents'] - assert_redirected_to '/projects/ecookbook/settings/modules' - assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort - end - - def test_destroy_leaf_project_without_confirmation_should_show_confirmation - @request.session[:user_id] = 1 # admin - - assert_no_difference 'Project.count' do - delete :destroy, :id => 2 - assert_response :success - assert_template 'destroy' - end - end - - def test_destroy_without_confirmation_should_show_confirmation_with_subprojects - @request.session[:user_id] = 1 # admin - - assert_no_difference 'Project.count' do - delete :destroy, :id => 1 - assert_response :success - assert_template 'destroy' - end - assert_select 'strong', - :text => ['Private child of eCookbook', - 'Child of private child, eCookbook Subproject 1', - 'eCookbook Subproject 2'].join(', ') - end - - def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects - @request.session[:user_id] = 1 # admin - - assert_difference 'Project.count', -5 do - delete :destroy, :id => 1, :confirm => 1 - assert_redirected_to '/admin/projects' - end - assert_nil Project.find_by_id(1) - end - - def test_archive - @request.session[:user_id] = 1 # admin - post :archive, :id => 1 - assert_redirected_to '/admin/projects' - assert !Project.find(1).active? - end - - def test_archive_with_failure - @request.session[:user_id] = 1 - Project.any_instance.stubs(:archive).returns(false) - post :archive, :id => 1 - assert_redirected_to '/admin/projects' - assert_match /project cannot be archived/i, flash[:error] - end - - def test_unarchive - @request.session[:user_id] = 1 # admin - Project.find(1).archive - post :unarchive, :id => 1 - assert_redirected_to '/admin/projects' - assert Project.find(1).active? - end - - def test_close - @request.session[:user_id] = 2 - post :close, :id => 1 - assert_redirected_to '/projects/ecookbook' - assert_equal Project::STATUS_CLOSED, Project.find(1).status - end - - def test_reopen - Project.find(1).close - @request.session[:user_id] = 2 - post :reopen, :id => 1 - assert_redirected_to '/projects/ecookbook' - assert Project.find(1).active? - end - - def test_project_breadcrumbs_should_be_limited_to_3_ancestors - CustomField.delete_all - parent = nil - 6.times do |i| - p = Project.generate_with_parent!(parent) - get :show, :id => p - assert_select '#header h1' do - assert_select 'a', :count => [i, 3].min - end - - parent = p - end - end - - def test_get_copy - @request.session[:user_id] = 1 # admin - get :copy, :id => 1 - assert_response :success - assert_template 'copy' - assert assigns(:project) - assert_equal Project.find(1).description, assigns(:project).description - assert_nil assigns(:project).id - - assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1 - end - - def test_get_copy_with_invalid_source_should_respond_with_404 - @request.session[:user_id] = 1 - get :copy, :id => 99 - assert_response 404 - end - - def test_post_copy_should_copy_requested_items - @request.session[:user_id] = 1 # admin - CustomField.delete_all - - assert_difference 'Project.count' do - post :copy, :id => 1, - :project => { - :name => 'Copy', - :identifier => 'unique-copy', - :tracker_ids => ['1', '2', '3', ''], - :enabled_module_names => %w(issue_tracking time_tracking) - }, - :only => %w(issues versions) - end - project = Project.find('unique-copy') - source = Project.find(1) - assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort - - assert_equal source.versions.count, project.versions.count, "All versions were not copied" - assert_equal source.issues.count, project.issues.count, "All issues were not copied" - assert_equal 0, project.members.count - end - - def test_post_copy_should_redirect_to_settings_when_successful - @request.session[:user_id] = 1 # admin - post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'} - assert_response :redirect - assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' - end - - def test_jump_should_redirect_to_active_tab - get :show, :id => 1, :jump => 'issues' - assert_redirected_to '/projects/ecookbook/issues' - end - - def test_jump_should_not_redirect_to_inactive_tab - get :show, :id => 3, :jump => 'documents' - assert_response :success - assert_template 'show' - end - - def test_jump_should_not_redirect_to_unknown_tab - get :show, :id => 3, :jump => 'foobar' - assert_response :success - assert_template 'show' - end - - def test_body_should_have_project_css_class - get :show, :id => 1 - assert_select 'body.project-ecookbook' - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/76/76f5b1657c4e0eb2717b1dfffe6b49bb8cd094b8.svn-base --- a/.svn/pristine/76/76f5b1657c4e0eb2717b1dfffe6b49bb8cd094b8.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +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 VersionCustomField < CustomField - def type_name - :label_version_plural - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/77/771517d2f5d100730b0ffcf7efa80c42ba739a96.svn-base --- a/.svn/pristine/77/771517d2f5d100730b0ffcf7efa80c42ba739a96.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,344 +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 AccountControllerTest < ActionController::TestCase - fixtures :users, :roles - - def setup - User.current = nil - end - - def test_get_login - get :login - assert_response :success - assert_template 'login' - - assert_select 'input[name=username]' - assert_select 'input[name=password]' - end - - def test_get_login_while_logged_in_should_redirect_to_home - @request.session[:user_id] = 2 - - get :login - assert_redirected_to '/' - assert_equal 2, @request.session[:user_id] - end - - def test_login_should_redirect_to_back_url_param - # request.uri is "test.host" in test environment - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.host/issues/show/1' - assert_redirected_to '/issues/show/1' - end - - def test_login_should_not_redirect_to_another_host - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.foo/fake' - assert_redirected_to '/my/page' - end - - def test_login_with_wrong_password - post :login, :username => 'admin', :password => 'bad' - assert_response :success - assert_template 'login' - - assert_select 'div.flash.error', :text => /Invalid user or password/ - assert_select 'input[name=username][value=admin]' - assert_select 'input[name=password]' - assert_select 'input[name=password][value]', 0 - end - - def test_login_with_locked_account_should_fail - User.find(2).update_attribute :status, User::STATUS_LOCKED - - post :login, :username => 'jsmith', :password => 'jsmith' - assert_redirected_to '/login' - assert_include 'locked', flash[:error] - assert_nil @request.session[:user_id] - end - - def test_login_as_registered_user_with_manual_activation_should_inform_user - User.find(2).update_attribute :status, User::STATUS_REGISTERED - - with_settings :self_registration => '2', :default_language => 'en' do - post :login, :username => 'jsmith', :password => 'jsmith' - assert_redirected_to '/login' - assert_include 'pending administrator approval', flash[:error] - end - end - - def test_login_as_registered_user_with_email_activation_should_propose_new_activation_email - User.find(2).update_attribute :status, User::STATUS_REGISTERED - - with_settings :self_registration => '1', :default_language => 'en' do - post :login, :username => 'jsmith', :password => 'jsmith' - assert_redirected_to '/login' - assert_equal 2, @request.session[:registered_user_id] - assert_include 'new activation email', flash[:error] - end - end - - def test_login_should_rescue_auth_source_exception - source = AuthSource.create!(:name => 'Test') - User.find(2).update_attribute :auth_source_id, source.id - AuthSource.any_instance.stubs(:authenticate).raises(AuthSourceException.new("Something wrong")) - - post :login, :username => 'jsmith', :password => 'jsmith' - assert_response 500 - assert_error_tag :content => /Something wrong/ - end - - def test_login_should_reset_session - @controller.expects(:reset_session).once - - post :login, :username => 'jsmith', :password => 'jsmith' - assert_response 302 - end - - def test_get_logout_should_not_logout - @request.session[:user_id] = 2 - get :logout - assert_response :success - assert_template 'logout' - - assert_equal 2, @request.session[:user_id] - end - - def test_get_logout_with_anonymous_should_redirect - get :logout - assert_redirected_to '/' - end - - def test_logout - @request.session[:user_id] = 2 - post :logout - assert_redirected_to '/' - assert_nil @request.session[:user_id] - end - - def test_logout_should_reset_session - @controller.expects(:reset_session).once - - @request.session[:user_id] = 2 - post :logout - assert_response 302 - end - - def test_get_register_with_registration_on - with_settings :self_registration => '3' do - get :register - assert_response :success - assert_template 'register' - assert_not_nil assigns(:user) - - assert_select 'input[name=?]', 'user[password]' - assert_select 'input[name=?]', 'user[password_confirmation]' - end - end - - def test_get_register_should_detect_user_language - with_settings :self_registration => '3' do - @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' - get :register - assert_response :success - assert_not_nil assigns(:user) - assert_equal 'fr', assigns(:user).language - assert_select 'select[name=?]', 'user[language]' do - assert_select 'option[value=fr][selected=selected]' - end - end - end - - def test_get_register_with_registration_off_should_redirect - with_settings :self_registration => '0' do - get :register - assert_redirected_to '/' - end - end - - # See integration/account_test.rb for the full test - def test_post_register_with_registration_on - with_settings :self_registration => '3' do - assert_difference 'User.count' do - post :register, :user => { - :login => 'register', - :password => 'secret123', - :password_confirmation => 'secret123', - :firstname => 'John', - :lastname => 'Doe', - :mail => 'register@example.com' - } - assert_redirected_to '/my/account' - end - user = User.first(:order => 'id DESC') - assert_equal 'register', user.login - assert_equal 'John', user.firstname - assert_equal 'Doe', user.lastname - assert_equal 'register@example.com', user.mail - assert user.check_password?('secret123') - assert user.active? - end - end - - def test_post_register_with_registration_off_should_redirect - with_settings :self_registration => '0' do - assert_no_difference 'User.count' do - post :register, :user => { - :login => 'register', - :password => 'test', - :password_confirmation => 'test', - :firstname => 'John', - :lastname => 'Doe', - :mail => 'register@example.com' - } - assert_redirected_to '/' - end - end - end - - def test_get_lost_password_should_display_lost_password_form - get :lost_password - assert_response :success - assert_select 'input[name=mail]' - end - - def test_lost_password_for_active_user_should_create_a_token - Token.delete_all - ActionMailer::Base.deliveries.clear - assert_difference 'ActionMailer::Base.deliveries.size' do - assert_difference 'Token.count' do - with_settings :host_name => 'mydomain.foo', :protocol => 'http' do - post :lost_password, :mail => 'JSmith@somenet.foo' - assert_redirected_to '/login' - end - end - end - - token = Token.order('id DESC').first - assert_equal User.find(2), token.user - assert_equal 'recovery', token.action - - assert_select_email do - assert_select "a[href=?]", "http://mydomain.foo/account/lost_password?token=#{token.value}" - end - end - - def test_lost_password_for_unknown_user_should_fail - Token.delete_all - assert_no_difference 'Token.count' do - post :lost_password, :mail => 'invalid@somenet.foo' - assert_response :success - end - end - - def test_lost_password_for_non_active_user_should_fail - Token.delete_all - assert User.find(2).lock! - - assert_no_difference 'Token.count' do - post :lost_password, :mail => 'JSmith@somenet.foo' - assert_redirected_to '/account/lost_password' - end - end - - def test_lost_password_for_user_who_cannot_change_password_should_fail - User.any_instance.stubs(:change_password_allowed?).returns(false) - - assert_no_difference 'Token.count' do - post :lost_password, :mail => 'JSmith@somenet.foo' - assert_response :success - end - end - - def test_get_lost_password_with_token_should_display_the_password_recovery_form - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - get :lost_password, :token => token.value - assert_response :success - assert_template 'password_recovery' - - assert_select 'input[type=hidden][name=token][value=?]', token.value - end - - def test_get_lost_password_with_invalid_token_should_redirect - get :lost_password, :token => "abcdef" - assert_redirected_to '/' - end - - def test_post_lost_password_with_token_should_change_the_user_password - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' - assert_redirected_to '/login' - user.reload - assert user.check_password?('newpass123') - assert_nil Token.find_by_id(token.id), "Token was not deleted" - end - - def test_post_lost_password_with_token_for_non_active_user_should_fail - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - user.lock! - - post :lost_password, :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' - assert_redirected_to '/' - assert ! user.check_password?('newpass123') - end - - def test_post_lost_password_with_token_and_password_confirmation_failure_should_redisplay_the_form - user = User.find(2) - token = Token.create!(:action => 'recovery', :user => user) - - post :lost_password, :token => token.value, :new_password => 'newpass', :new_password_confirmation => 'wrongpass' - assert_response :success - assert_template 'password_recovery' - assert_not_nil Token.find_by_id(token.id), "Token was deleted" - - assert_select 'input[type=hidden][name=token][value=?]', token.value - end - - def test_post_lost_password_with_invalid_token_should_redirect - post :lost_password, :token => "abcdef", :new_password => 'newpass', :new_password_confirmation => 'newpass' - assert_redirected_to '/' - end - - def test_activation_email_should_send_an_activation_email - User.find(2).update_attribute :status, User::STATUS_REGISTERED - @request.session[:registered_user_id] = 2 - - with_settings :self_registration => '1' do - assert_difference 'ActionMailer::Base.deliveries.size' do - get :activation_email - assert_redirected_to '/login' - end - end - end - - def test_activation_email_without_session_data_should_fail - User.find(2).update_attribute :status, User::STATUS_REGISTERED - - with_settings :self_registration => '1' do - assert_no_difference 'ActionMailer::Base.deliveries.size' do - get :activation_email - assert_redirected_to '/' - end - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/77/7727bb04d0abb3e8e4031848fb82a82cb998f0af.svn-base --- a/.svn/pristine/77/7727bb04d0abb3e8e4031848fb82a82cb998f0af.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/77/77b5f4fec79f0d6eb61a76e965e02cb7504641fe.svn-base --- a/.svn/pristine/77/77b5f4fec79f0d6eb61a76e965e02cb7504641fe.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,306 +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 RepositoryBazaarTest < ActiveSupport::TestCase - fixtures :projects - - include Redmine::I18n - - REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s - REPOSITORY_PATH_TRUNK = File.join(REPOSITORY_PATH, "trunk") - NUM_REV = 4 - - REPOSITORY_PATH_NON_ASCII = Rails.root.join(REPOSITORY_PATH + '/' + 'non_ascii').to_s - - # Bazaar core does not support xml output such as Subversion and Mercurial. - # "bzr" command output and command line parameter depend on locale. - # So, non ASCII path tests cannot run independent locale. - # - # If you want to run Bazaar non ASCII path tests on Linux *Ruby 1.9*, - # you need to set locale character set "ISO-8859-1". - # E.g. "LANG=en_US.ISO-8859-1". - # On Linux other platforms (e.g. Ruby 1.8, JRuby), - # you need to set "RUN_LATIN1_OUTPUT_TEST = true" manually. - # - # On Windows, because it is too hard to change system locale, - # you cannot run Bazaar non ASCII path tests. - # - RUN_LATIN1_OUTPUT_TEST = (RUBY_PLATFORM != 'java' && - REPOSITORY_PATH.respond_to?(:force_encoding) && - Encoding.locale_charmap == "ISO-8859-1") - - CHAR_1_UTF8_HEX = "\xc3\x9c" - CHAR_1_LATIN1_HEX = "\xdc" - - def setup - @project = Project.find(3) - @repository = Repository::Bazaar.create( - :project => @project, :url => REPOSITORY_PATH_TRUNK, - :log_encoding => 'UTF-8') - assert @repository - @char_1_utf8 = CHAR_1_UTF8_HEX.dup - @char_1_ascii8bit = CHAR_1_LATIN1_HEX.dup - if @char_1_utf8.respond_to?(:force_encoding) - @char_1_utf8.force_encoding('UTF-8') - @char_1_ascii8bit.force_encoding('ASCII-8BIT') - end - end - - def test_blank_path_to_repository_error_message - set_language_if_valid 'en' - repo = Repository::Bazaar.new( - :project => @project, - :identifier => 'test', - :log_encoding => 'UTF-8' - ) - assert !repo.save - assert_include "Path to repository can't be blank", - repo.errors.full_messages - end - - def test_blank_path_to_repository_error_message_fr - set_language_if_valid 'fr' - str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" - str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) - repo = Repository::Bazaar.new( - :project => @project, - :url => "", - :identifier => 'test', - :log_encoding => 'UTF-8' - ) - assert !repo.save - assert_include str, repo.errors.full_messages - end - - if File.directory?(REPOSITORY_PATH_TRUNK) - def test_fetch_changesets_from_scratch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - - assert_equal NUM_REV, @repository.changesets.count - assert_equal 9, @repository.filechanges.count - assert_equal 'Initial import', @repository.changesets.find_by_revision('1').comments - end - - def test_fetch_changesets_incremental - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - # Remove changesets with revision > 5 - @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} - @project.reload - assert_equal 2, @repository.changesets.count - - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - end - - def test_entries - entries = @repository.entries - assert_kind_of Redmine::Scm::Adapters::Entries, entries - assert_equal 2, entries.size - - assert_equal 'dir', entries[0].kind - assert_equal 'directory', entries[0].name - assert_equal 'directory', entries[0].path - - assert_equal 'file', entries[1].kind - assert_equal 'doc-mkdir.txt', entries[1].name - assert_equal 'doc-mkdir.txt', entries[1].path - end - - def test_entries_in_subdirectory - entries = @repository.entries('directory') - assert_equal 3, entries.size - - assert_equal 'file', entries.last.kind - assert_equal 'edit.png', entries.last.name - assert_equal 'directory/edit.png', entries.last.path - end - - def test_previous - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('3') - assert_equal @repository.find_changeset_by_name('2'), changeset.previous - end - - def test_previous_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('1') - assert_nil changeset.previous - end - - def test_next - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('2') - assert_equal @repository.find_changeset_by_name('3'), changeset.next - end - - def test_next_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('4') - assert_nil changeset.next - end - - if File.directory?(REPOSITORY_PATH_NON_ASCII) && RUN_LATIN1_OUTPUT_TEST - def test_cat_latin1_path - latin1_repo = create_latin1_repo - buf = latin1_repo.cat( - "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) - assert buf - lines = buf.split("\n") - assert_equal 2, lines.length - assert_equal 'It is written in Python.', lines[1] - - buf = latin1_repo.cat( - "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) - assert buf - lines = buf.split("\n") - assert_equal 1, lines.length - assert_equal "test-#{@char_1_ascii8bit}.txt", lines[0] - end - - def test_annotate_latin1_path - latin1_repo = create_latin1_repo - ann1 = latin1_repo.annotate( - "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) - assert_equal 2, ann1.lines.size - assert_equal '2', ann1.revisions[0].identifier - assert_equal 'test00@', ann1.revisions[0].author - assert_equal 'It is written in Python.', ann1.lines[1] - ann2 = latin1_repo.annotate( - "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) - assert_equal 1, ann2.lines.size - assert_equal '2', ann2.revisions[0].identifier - assert_equal 'test00@', ann2.revisions[0].author - assert_equal "test-#{@char_1_ascii8bit}.txt", ann2.lines[0] - end - - def test_diff_latin1_path - latin1_repo = create_latin1_repo - diff1 = latin1_repo.diff( - "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2, 1) - assert_equal 7, diff1.size - buf = diff1[5].gsub(/\r\n|\r|\n/, "") - assert_equal "+test-#{@char_1_ascii8bit}.txt", buf - end - - def test_entries_latin1_path - latin1_repo = create_latin1_repo - entries = latin1_repo.entries("test-#{@char_1_utf8}-dir", 2) - assert_kind_of Redmine::Scm::Adapters::Entries, entries - assert_equal 3, entries.size - assert_equal 'file', entries[1].kind - assert_equal "test-#{@char_1_utf8}-1.txt", entries[0].name - assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", entries[0].path - end - - def test_entry_latin1_path - latin1_repo = create_latin1_repo - ["test-#{@char_1_utf8}-dir", - "/test-#{@char_1_utf8}-dir", - "/test-#{@char_1_utf8}-dir/" - ].each do |path| - entry = latin1_repo.entry(path, 2) - assert_equal "test-#{@char_1_utf8}-dir", entry.path - assert_equal "dir", entry.kind - end - ["test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", - "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt" - ].each do |path| - entry = latin1_repo.entry(path, 2) - assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", - entry.path - assert_equal "file", entry.kind - end - end - - def test_changeset_latin1_path - latin1_repo = create_latin1_repo - assert_equal 0, latin1_repo.changesets.count - latin1_repo.fetch_changesets - @project.reload - assert_equal 3, latin1_repo.changesets.count - - cs2 = latin1_repo.changesets.find_by_revision('2') - assert_not_nil cs2 - assert_equal "test-#{@char_1_utf8}", cs2.comments - c2 = cs2.filechanges.sort_by(&:path) - assert_equal 4, c2.size - assert_equal 'A', c2[0].action - assert_equal "/test-#{@char_1_utf8}-dir/", c2[0].path - assert_equal 'A', c2[1].action - assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", c2[1].path - assert_equal 'A', c2[2].action - assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c2[2].path - assert_equal 'A', c2[3].action - assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c2[3].path - - cs3 = latin1_repo.changesets.find_by_revision('3') - assert_not_nil cs3 - assert_equal "modify, move and delete #{@char_1_utf8} files", cs3.comments - c3 = cs3.filechanges.sort_by(&:path) - assert_equal 3, c3.size - assert_equal 'M', c3[0].action - assert_equal "/test-#{@char_1_utf8}-1.txt", c3[0].path - assert_equal 'D', c3[1].action - assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c3[1].path - assert_equal 'M', c3[2].action - assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c3[2].path - end - else - msg = "Bazaar non ASCII output test cannot run this environment." + "\n" - if msg.respond_to?(:force_encoding) - msg += "Encoding.locale_charmap: " + Encoding.locale_charmap + "\n" - end - puts msg - end - - private - - def create_latin1_repo - repo = Repository::Bazaar.create( - :project => @project, - :identifier => 'latin1', - :url => REPOSITORY_PATH_NON_ASCII, - :log_encoding => 'ISO-8859-1' - ) - assert repo - repo - end - else - puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/78/78376d3215166c5e884949655b3cf6e5caa6e30e.svn-base --- a/.svn/pristine/78/78376d3215166c5e884949655b3cf6e5caa6e30e.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/78/7884d1a52f46dcfb203fc57885fb084b78063ba1.svn-base --- a/.svn/pristine/78/7884d1a52f46dcfb203fc57885fb084b78063ba1.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,242 +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/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 e248c7af89ec -r b450a9d58aed .svn/pristine/78/788c80634e0b1d20f293008ce93f6cb1d4ce218a.svn-base --- a/.svn/pristine/78/788c80634e0b1d20f293008ce93f6cb1d4ce218a.svn-base Mon Mar 17 08:54:02 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. - -class JournalDetail < ActiveRecord::Base - belongs_to :journal - before_save :normalize_values - - def custom_field - if property == 'cf' - @custom_field ||= CustomField.find_by_id(prop_key) - end - end - - private - - def normalize_values - self.value = normalize(value) - self.old_value = normalize(old_value) - end - - def normalize(v) - case v - when true - "1" - when false - "0" - when Date - v.strftime("%Y-%m-%d") - else - v - end - end -end diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/78/78d4b11a09b045fdd7cd2b6a434ac47f6be2b36b.svn-base --- a/.svn/pristine/78/78d4b11a09b045fdd7cd2b6a434ac47f6be2b36b.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +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 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 e248c7af89ec -r b450a9d58aed .svn/pristine/79/791795b2319be82f4ccbdcd799aa17c64410dd34.svn-base --- a/.svn/pristine/79/791795b2319be82f4ccbdcd799aa17c64410dd34.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +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. - -namespace :redmine do - namespace :attachments do - desc 'Removes uploaded files left unattached after one day.' - task :prune => :environment do - Attachment.prune - end - - desc 'Moves attachments stored at the root of the file directory (ie. created before Redmine 2.3) to their subdirectories' - task :move_to_subdirectories => :environment do - Attachment.move_from_root_to_target_directory - end - end - - namespace :tokens do - desc 'Removes expired tokens.' - task :prune => :environment do - Token.destroy_expired - end - end - - namespace :watchers do - desc 'Removes watchers from what they can no longer view.' - task :prune => :environment do - Watcher.prune - end - end - - desc 'Fetch changesets from the repositories' - task :fetch_changesets => :environment do - Repository.fetch_changesets - end - - desc 'Migrates and copies plugins assets.' - task :plugins do - Rake::Task["redmine:plugins:migrate"].invoke - Rake::Task["redmine:plugins:assets"].invoke - end - - namespace :plugins do - desc 'Migrates installed plugins.' - task :migrate => :environment do - name = ENV['NAME'] - version = nil - version_string = ENV['VERSION'] - if version_string - if version_string =~ /^\d+$/ - version = version_string.to_i - if name.nil? - abort "The VERSION argument requires a plugin NAME." - end - else - abort "Invalid VERSION #{version_string} given." - end - end - - begin - Redmine::Plugin.migrate(name, version) - rescue Redmine::PluginNotFound - abort "Plugin #{name} was not found." - end - - Rake::Task["db:schema:dump"].invoke - end - - desc 'Copies plugins assets into the public directory.' - task :assets => :environment do - name = ENV['NAME'] - - begin - Redmine::Plugin.mirror_assets(name) - rescue Redmine::PluginNotFound - abort "Plugin #{name} was not found." - end - end - - desc 'Runs the plugins tests.' - task :test do - Rake::Task["redmine:plugins:test:units"].invoke - Rake::Task["redmine:plugins:test:functionals"].invoke - Rake::Task["redmine:plugins:test:integration"].invoke - end - - namespace :test do - desc 'Runs the plugins unit tests.' - Rake::TestTask.new :units => "db:test:prepare" do |t| - t.libs << "test" - t.verbose = true - t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/unit/**/*_test.rb" - end - - desc 'Runs the plugins functional tests.' - Rake::TestTask.new :functionals => "db:test:prepare" do |t| - t.libs << "test" - t.verbose = true - t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/functional/**/*_test.rb" - end - - desc 'Runs the plugins integration tests.' - Rake::TestTask.new :integration => "db:test:prepare" do |t| - t.libs << "test" - t.verbose = true - t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/integration/**/*_test.rb" - end - end - end -end - -# Load plugins' rake tasks -Dir[File.join(Rails.root, "plugins/*/lib/tasks/**/*.rake")].sort.each { |ext| load ext } diff -r e248c7af89ec -r b450a9d58aed .svn/pristine/79/79599156ceffeaaae36ac3fa01857d9ccf943ee2.svn-base --- a/.svn/pristine/79/79599156ceffeaaae36ac3fa01857d9ccf943ee2.svn-base Mon Mar 17 08:54:02 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2642 +0,0 @@ -== Redmine changelog - -Redmine - project management software -Copyright (C) 2006-2013 Jean-Philippe Lang -http://www.redmine.org/ - -== 2013-12-23 v2.4.2 - -* Defect #15398: HTML 5 invalid
    tag -* Defect #15523: CSS class for done ratio is not properly generated -* Defect #15623: Timelog filtering by activity field does not handle project activity overrides -* Defect #15677: Links for relations in notifications do not include hostname -* Defect #15684: MailHandler : text/plain attachments are added to description -* Defect #15714: Notification on loosing assignment does not work -* Defect #15735: OpenID login fails due to CSRF verification -* Defect #15741: Multiple scrollbars in project selection tree -* Patch #9442: Russian wiki syntax help translations -* Patch #15524: Japanese translation update (r12278) -* Patch #15601: Turkish translation update -* Patch #15688: Spanish translation updated -* Patch #15696: Russian translation update - -== 2013-11-23 v2.4.1 - -* Defect #15401: Wiki syntax "bold italic" is incorrect -* Defect #15414: Empty sidebar should not be displayed in project overview -* Defect #15427: REST API POST and PUT broken -* Patch #15376: Traditional Chinese translation (to r12295) -* Patch #15395: German "ImageMagick convert available" translation -* Patch #15400: Czech Wiki syntax traslation -* Patch #15402: Czech translation for 2.4-stable - -== 2013-11-17 v2.4.0 - -* Defect #1983: statistics get rather cramped with more than 15 or so contributers -* Defect #7335: Sorting issues in gantt by date, not by id -* Defect #12681: Treat group assignments as assigned to me -* Defect #12824: Useless "edit" link in workflow menu -* Defect #13260: JQuery Datepicker popup is missing multiple month/year modifiers -* Defect #13537: Filters will show issues with unused custom fields. -* Defect #13829: Favicon bug in IE8 -* Defect #13949: Handling of attachment uploads when 'Maximum attachment size' is set to 0 -* Defect #13989: Trac and Mantis importers reset global notification settings -* Defect #13990: Trac importer breaks on exotic filenames and ruby 1.9+ -* Defect #14028: Plugins Gemfiles loading breaks __FILE__ -* Defect #14086: Better handling of issue start date validation -* Defect #14206: Synchronize the lang attribute of the HTML with the display language -* Defect #14403: No error message if notification mail could not delivered -* Defect #14516: Missing Sort Column Label and Center Align on Admin-Enumerations -* Defect #14517: Missing Html Tile on Admin (Groups, LDAP and Plugins) -* Defect #14598: Wrong test with logger.info in model mail_handler -* Defect #14615: Warn me when leaving a page with unsaved text doesn't work when editing an update note -* Defect #14621: AJAX call on the issue form resets data entered during the request -* Defect #14657: Wrong German translation for member inheritance -* Defect #14773: ActiveRecord::Acts::Versioned::ActMethods#next_version Generates ArgumentError -* Defect #14819: Newlines in attachment filename causes crash -* Defect #14986: 500 error when viewing a wiki page without WikiContent -* Defect #14995: Japanese "notice_not_authorized" translation is incorrect -* Defect #15044: Patch for giving controller_issues_edit_after_save api hook the correct context -* Defect #15050: redmine:migrate_from_mantis fails to migrate projects with all upper case name -* Defect #15058: Project authorization EnabledModule N+1 queries -* Defect #15113: The mail method should return a Mail::Message -* Defect #15135: Issue#update_nested_set_attributes comparing nil with empty string -* Defect #15191: HTML 5 validation failures -* Defect #15227: Custom fields in issue form - splitting is incorrect -* Defect #15307: HTML 5 deprecates width and align attributes -* Feature #1005: Add the addition/removal/change of related issues to the history -* Feature #1019: Role based custom queries -* Feature #1391: Ability to force user to change password -* Feature #2199: Ability to clear dates and text fields when bulk editing issues -* Feature #2427: Document horizontal rule syntax -* Feature #2795: Add a "Cancel" button to the "Delete" project page when deleting a project. -* Feature #2865: One click filter in search view -* Feature #3413: Exclude attachments from incoming emails based on file name -* Feature #3872: New user password - better functionality -* Feature #4911: Multiple issue update rules with different keywords in commit messages -* Feature #5037: Role-based issue custom field visibility -* Feature #7590: Different commit Keywords for each tracker -* Feature #7836: Ability to save Gantt query filters -* Feature #8253: Update CodeRay to 1.1 final -* Feature #11159: REST API for getting CustomField definitions -* Feature #12293: Add links to attachments in new issue email notification -* Feature #12912: Issue-notes Redmine links: append actual note reference to rendered links -* Feature #13157: Link on "My Page" to view all my spent time -* Feature #13746: Highlighting of source link target line -* Feature #13943: Better handling of validation errors when bulk editing issues -* Feature #13945: Disable autofetching of repository changesets if projects are closed -* Feature #14024: Default of issue start and due date -* Feature #14060: Enable configuration of OpenIdAuthentication.store -* Feature #14228: Registered users should have a way to get a new action email -* Feature #14614: View hooks for user preferences -* Feature #14630: wiki_syntax.html per language (wiki help localization mechanism) -* Feature #15136: Activate Custom Fields on a selection of projects directly from Custom fields page -* Feature #15182: Return to section anchor after wiki section edit -* Feature #15218: Update Rails 3.2.15 -* Feature #15311: Add an indication to admin/info whether or not ImageMagick convert is available -* Patch #6689: Document project-links in parse_redmine_links -* Patch #13460: All translations: RSS -> Atom -* Patch #13482: Do not add empty header/footer to notification emails -* Patch #13528: Traditional Chinese "label_total_time" translation -* Patch #13551: update Dutch translations - March 2013 -* Patch #13577: Japanese translation improvement ("done ratio") -* Patch #13646: Fix handling multiple text parts in email -* Patch #13674: Lithuanian translation -* Patch #13687: Favicon bug in opera browser -* Patch #13697: Back-button on diff page is not working when I'm directed from email -* Patch #13745: Correct translation for member save button -* Patch #13808: Changed Bulgarian "label_statistics" translation -* Patch #13825: German translation: jquery.ui.datepicker-de.js -* Patch #13900: Update URL when changing tab -* Patch #13931: Error and inconsistencies in Croatian translation -* Patch #13948: REST API should return user.status -* Patch #13988: Enhanced Arabic translation -* Patch #14138: Output changeset comment in html title -* Patch #14180: Improve pt-BR translation -* Patch #14222: German translation: grammar + spelling -* Patch #14223: Fix icon transparency issues -* Patch #14360: Slovene language translation -* Patch #14767: More CSS classes on various fields -* Patch #14901: Slovak translation -* Patch #14920: Russian numeric translation -* Patch #14981: Italian translation -* Patch #15072: Optimization of issues journal custom fields display -* Patch #15073: list custom fields : multiple select filter wider -* Patch #15075: Fix typo in the Dutch "label_user_mail_option_all" translation -* Patch #15277: Accept custom field format added at runtime -* Patch #15295: Log error messages when moving attachements in sub-directories -* Patch #15369: Bulgarian translation (r12278) - -== 2013-11-17 v2.3.4 - -* Defect #13348: Repository tree can't handle two loading at once -* Defect #13632: Empty page attached when exporting PDF -* Defect #14590: migrate_from_trac.rake does not import Trac users, uses too short password -* Defect #14656: JRuby: Encoding error when creating issues -* Defect #14883: Update activerecord-jdbc-adapter -* Defect #14902: Potential invalid SQL error with invalid group_ids -* Defect #14931: SCM annotate with non ASCII author -* Defect #14960: migrate_from_mantis.rake does not import Mantis users, uses too short password -* Defect #14977: Internal Server Error while uploading file -* Defect #15190: JS-error while using a global custom query w/ project filter in a project context -* Defect #15235: Wiki Pages REST API with version returns wrong comments -* Defect #15344: Default status always inserted to allowed statuses when changing status -* Feature #14919: Update ruby-openid version above 2.3.0 -* Patch #14592: migrate_from_trac.rake does not properly parse First Name and Last Name -* Patch #14886: Norweigan - label_copied_to and label_copied_from translated -* Patch #15185: Simplified Chinese translation for 2.3-stable - -== 2013-09-14 v2.3.3 - -* Defect #13008: Usage of attribute_present? in UserPreference -* Defect #14340: Autocomplete fields rendering issue with alternate theme -* Defect #14366: Spent Time report sorting on custom fields causes error -* Defect #14369: Open/closed issue counts on issues summary are not displayed with SQLServer -* Defect #14401: Filtering issues on "related to" may ignore other filters -* Defect #14415: Spent time details and report should ignore 'Setting.display_subprojects_issues?' when 'Subproject' filter is enabled. -* Defect #14422: CVS root_url not recognized when connection string does not include port -* Defect #14447: Additional status transitions for assignees do not work if assigned to a group -* Defect #14511: warning: class variable access from toplevel on Ruby 2.0 -* Defect #14562: diff of CJK (Chinese/Japanese/Korean) is broken on Ruby 1.8 -* Defect #14584: Standard fields disabled for certain trackers still appear in email notifications -* Defect #14607: rake redmine:load_default_data Error -* Defect #14697: Wrong Russian translation in close project message -* Defect #14798: Wrong done_ratio calculation for parent with subtask having estimated_hours=0 -* Patch #14485: Traditional Chinese translation for 2.3-stable -* Patch #14502: Russian translation for 2.3-stable -* Patch #14531: Spanish translations for 2.3.x -* Patch #14686: Portuguese translation for 2.3-stable - -== 2013-07-14 v2.3.2 - -* Defect #9996: configuration.yml in documentation , but redmine ask me to create email.yml -* Defect #13692: warning: already initialized constant on Ruby 1.8.7 -* Defect #13783: Internal error on time tracking activity enumeration deletion -* Defect #13821: "obj" parameter is not defined for macros used in description of documents -* Defect #13850: Unable to set custom fields for versions using the REST API -* Defect #13910: Values of custom fields are not kept in issues when copying a project -* Defect #13950: Duplicate Lithuanian "error_attachment_too_big" translation keys -* Defect #14015: Ruby hangs when adding a subtask -* Defect #14020: Locking and unlocking a user resets the email notification checkbox -* Defect #14023: Can't delete relation when Redmine runs in a subpath -* Defect #14051: Filtering issues with custom field in date format with NULL(empty) value -* Defect #14178: PDF API broken in version 2.3.1 -* Defect #14186: Project name is not properly escaped in issue filters JSON -* Defect #14242: Project auto generation fails when projects created in the same time -* Defect #14245: Gem::InstallError: nokogiri requires Ruby version >= 1.9.2. -* Defect #14346: Latvian translation for "Log time" -* Feature #12888: Adding markings to emails generated by Private comments -* Feature #14419: Include RUBY_PATCHLEVEL and RUBY_RELEASE_DATE in info.rb -* Patch #14005: Swedish Translation for 2.3-stable -* Patch #14101: Receive IMAP by uid's -* Patch #14103: Disconnect and logout from IMAP after mail receive -* Patch #14145: German translation of x_hours -* Patch #14182: pt-BR translation for 2.3-stable -* Patch #14196: Italian translation for 2.3-stable -* Patch #14221: Translation of x_hours for many languages - -== 2013-05-01 v2.3.1 - -* Defect #12650: Lost text after selection in issue list with IE -* Defect #12684: Hotkey for Issue-Edit doesn't work as expected -* Defect #13405: Commit link title is escaped twice when using "commit:" prefix -* Defect #13541: Can't access SCM when log/production.scm.stderr.log is not writable -* Defect #13579: Datepicker uses Simplified Chinese in Traditional Chinese locale -* Defect #13584: Missing Portuguese jQuery UI date picker -* Defect #13586: Circular loop testing prevents precedes/follows relation between subtasks -* Defect #13618: CSV export of spent time ignores filters and columns selection -* Defect #13630: PDF export generates the issue id twice -* Defect #13644: Diff - Internal Error -* Defect #13712: Fix email rake tasks to also support no_account_notice and default_group options -* Defect #13811: Broken javascript in IE7 ; recurrence of #12195 -* Defect #13823: Trailing comma in javascript files -* Patch #13531: Traditional Chinese translation for 2.3-stable -* Patch #13552: Dutch translations for 2.3-stable -* Patch #13678: Lithuanian translation for 2.3-stable - -== 2013-03-19 v2.3.0 - -* Defect #3107: Issue with two digit year on Logtime -* Defect #3371: Autologin does not work when using openid -* Defect #3676: www. generates broken link in formatted text -* Defect #4700: Adding news does not send notification to all project members -* Defect #5329: Time entries report broken on first week of year -* Defect #8794: Circular loop when using relations and subtasks -* Defect #9475: German Translation "My custom queries" and "Custom queries" -* Defect #9549: Only 100 users are displayed when adding new project members -* Defect #10277: Redmine wikitext URL-into-link creation with hyphen is wrong -* Defect #10364: Custom field float separator in CSV export -* Defect #10930: rake redmine:load_default_data error in 2.0 with SQLServer -* Defect #10977: Redmine shouldn't require all database gems -* Defect #12528: Handle temporary failures gracefully in the external mail handler script -* Defect #12629: Wrong German "label_issues_by" translation -* Defect #12641: Diff outputs become ??? in some non ASCII words. -* Defect #12707: Typo in app/models/tracker.rb -* Defect #12716: Attachment description lost when issue validation fails -* Defect #12735: Negative duration allowed -* Defect #12736: Negative start/due dates allowed -* Defect #12968: Subtasks don't resepect following/precedes -* Defect #13006: Filter "Assignee's group" doesn't work with group assignments -* Defect #13022: Image pointing towards /logout signs out user -* Defect #13059: Custom fields are listed two times in workflow/Fields permission -* Defect #13076: Project overview page shows trackers from subprojects with disabled issue module -* Defect #13119: custom_field_values are not reloaded on #reload -* Defect #13154: After upgrade to 2.2.2 ticket list on some projects fails -* Defect #13188: Forms are not updated after changing the status field without "Add issues" permission -* Defect #13251: Adding a "follows" relation may not refresh relations list -* Defect #13272: translation missing: setting_default_projects_tracker_ids -* Defect #13328: Copying an issue as a child of itself creates an extra issue -* Defect #13335: Autologin does not work with custom autologin cookie name -* Defect #13350: Japanese mistranslation fix -* Feature #824: Add "closed_on" issue field (storing time of last closing) & add it as a column and filter on the issue list. -* Feature #1766: Custom fields should become addable to Spent Time list/report -* Feature #3436: Show relations in Gantt diagram -* Feature #3957: Ajax file upload with progress bar -* Feature #5298: Store attachments in sub directories -* Feature #5605: Subprojects should (optionally) inherit Members from their parent -* Feature #6727: Add/remove issue watchers via REST API -* Feature #7159: Bulk watch/unwatch issues from the context menu -* Feature #8529: Get the API key of the user through REST API -* Feature #8579: Multiple file upload with HTML5 / Drag-and-Drop -* Feature #10191: Add Filters For Spent time's Details and Report -* Feature #10286: Auto-populate fields while creating a new user with LDAP -* Feature #10352: Preview should already display the freshly attached images -* Feature #11498: Add --no-account-notice option for the mail handler script -* Feature #12122: Gantt progress lines (html only) -* Feature #12228: JRuby 1.7.2 support -* Feature #12251: Custom fields: 'Multiple values' should be able to be checked and then unchecked -* Feature #12401: Split "Manage documents" permission into create, edit and delete permissions -* Feature #12542: Group events in the activity view -* Feature #12665: Link to a file in a repository branch -* Feature #12713: Microsoft SQLServer support -* Feature #12787: Remove "Warning - iconv will be deprecated in the future, use String#encode instead." -* Feature #12843: Add links to projects in Group projects list -* Feature #12898: Handle GET /issues/context_menu parameters nicely to prevent returning error 500 to crawlers -* Feature #12992: Make JSONP support optional and disabled by default -* Feature #13174: Raise group name maximum length to 255 characters -* Feature #13175: Possibility to define the default enable trackers when creating a project -* Feature #13329: Ruby 2.0 support -* Feature #13337: Split translation "label_total" -* Feature #13340: Mail handler: option to add created user to default group -* Feature #13341: Mail handler: --no-notification option to disable notifications to the created user -* Patch #7202: Polish translation for v1.0.4 -* Patch #7851: Italian translation for 'issue' -* Patch #9225: Generate project identifier automatically with JavaScript -* Patch #10916: Optimisation in issues relations display -* Patch #12485: Don't force english language for default admin account -* Patch #12499: Use lambda in model scopes -* Patch #12611: Login link unexpected logs you out -* Patch #12626: Updated Japanese translations for button_view and permission_commit_access -* Patch #12640: Russian "about_x_hours" translation change -* Patch #12645: Russian numeric translation -* Patch #12660: Consistent German translation for my page -* Patch #12708: Restructured german translation (Cleanup) -* Patch #12721: Optimize MenuManager a bit -* Patch #12725: Change pourcent to percent (#12724) -* Patch #12754: Updated Japanese translation for notice_account_register_done -* Patch #12788: Copyright for 2013 -* Patch #12806: Serbian translation change -* Patch #12810: Swedish Translation change -* Patch #12910: Plugin settings div should perhaps have 'settings' CSS class -* Patch #12911: Fix 500 error for requests to the settings path for non-configurable plugins -* Patch #12926: Bulgarian translation (r11218) -* Patch #12927: Swedish Translation for r11244 -* Patch #12967: Change Spanish login/logout translations -* Patch #12988: Russian translation for trunk -* Patch #13080: German translation of label_in -* Patch #13098: Small datepicker improvements -* Patch #13152: Locale file for Azerbaijanian language -* Patch #13155: Add login to /users/:id API for current user -* Patch #13173: Put source :rubygems url HTTP secure -* Patch #13190: Bulgarian translation (r11404) -* Patch #13198: Traditional Chinese language file (to r11426) -* Patch #13203: German translation change for follow and precedes is inconsitent -* Patch #13206: Portuguese translation file -* Patch #13246: Some german translation patches -* Patch #13280: German translation (r11478) -* Patch #13301: Performance: avoid querying all memberships in User#roles_for_project -* Patch #13309: Add "tracker-[id]" CSS class to issues -* Patch #13324: fixing some pt-br locales -* Patch #13339: Complete language Vietnamese file -* Patch #13391: Czech translation update -* Patch #13399: Fixed some wrong or confusing translation in Korean locale -* Patch #13414: Bulgarian translation (r11567) -* Patch #13420: Korean translation for 2.3 (r11583) -* Patch #13437: German translation of setting_emails_header -* Patch #13438: English translation -* Patch #13447: German translation - some patches -* Patch #13450: Czech translation -* Patch #13475: fixing some pt-br locales -* Patch #13514: fixing some pt-br locales - -== 2013-03-19 v2.2.4 - -* Upgrade to Rails 3.2.13 -* Defect #12243: Ordering forum replies by last reply date is broken -* Defect #13127: h1 multiple lined titles breaks into main menu -* Defect #13138: Generating PDF of issue causes UndefinedConversionError with htmlentities gem -* Defect #13165: rdm-mailhandler.rb: initialize_http_header override basic auth -* Defect #13232: Link to topic in nonexistent forum causes error 500 -* Patch #13181: Bulgarian translation of jstoolbar-bg.js -* Patch #13207: Portuguese translation for 2.2-stable -* Patch #13310: pt-BR label_last_n_weeks translation -* Patch #13325: pt-BR translation for 2.2-stable -* Patch #13343: Vietnamese translation for 2.2-stable -* Patch #13398: Czech translation for 2.2-stable - -== 2013-02-12 v2.2.3 - -* Upgrade to Rails 3.2.12 -* Defect #11987: pdf: Broken new line in table -* Defect #12930: 404 Error when referencing different project source files in the wiki syntax -* Defect #12979: Wiki link syntax commit:repo_a:abcd doesn't work -* Defect #13075: Can't clear custom field value through context menu in the issue list -* Defect #13097: Project copy fails when wiki module is disabled -* Defect #13126: Issue view: estimated time vs. spent time -* Patch #12922: Update Spanish translation -* Patch #12928: Bulgarian translation for 2.2-stable -* Patch #12987: Russian translation for 2.2-stable - -== 2013-01-20 v2.2.2 - -* Defect #7510: Link to attachment should return latest attachment -* Defect #9842: {{toc}} is not replaced by table of content when exporting wiki page to pdf -* Defect #12749: Plugins cannot route wiki page sub-path -* Defect #12799: Cannot edit a wiki section which title starts with a tab -* Defect #12801: Viewing the history of a wiki page with attachments raises an error -* Defect #12833: Input fields restricted on length should have maxlength parameter set -* Defect #12838: Blank page when clicking Add with no block selected on my page layout -* Defect #12851: "Parent task is invalid" while editing child issues by Role with restricted Issues Visibility -* Patch #12800: Serbian Latin translation patch (sr-YU.yml) -* Patch #12809: Swedish Translation for r11162 -* Patch #12818: Minor swedish translation fix - -== 2013-01-09 v2.2.1 - -* Upgrade to Rails 3.2.11 -* Defect #12652: "Copy ticket" selects "new ticket" -* Defect #12691: Textile Homepage Dead? -* Defect #12711: incorrect fix of lib/SVG/Graph/TimeSeries.rb -* Defect #12744: Unable to call a macro with a name that contains uppercase letters -* Defect #12776: Security vulnerability in Rails 3.2.10 (CVE-2013-0156) -* Patch #12630: Russian "x_hours" translation - -== 2012-12-18 v2.2.0 - -* Defect #4787: Gannt to PNG - CJK (Chinese, Japanese and Korean) characters appear as ? -* Defect #8106: Issues by Category should show tasks without category -* Defect #8373: i18n string text_are_you_sure_with_children no longer used -* Defect #11426: Filtering with Due Date in less than N days should show overdue issues -* Defect #11834: Bazaar: "???" instead of non ASCII character in paths on non UTF-8 locale -* Defect #11868: Git and Mercurial diff displays deleted files as /dev/null -* Defect #11979: No validation errors when entering an invalid "Parent task" -* Defect #12012: Redmine::VERSION.revision method does not work on Subversion 1.7 working copy -* Defect #12018: Issue filter select box order changes randomly -* Defect #12090: email recipients not written to action_mailer log if BCC recipients setting is checked -* Defect #12092: Issue "start date" validation does not work correctly -* Defect #12285: Some unit and functional tests miss fixtures and break when run alone -* Defect #12286: Emails of private notes are sent to watcher users regardless of viewing permissions -* Defect #12310: Attachments may not be displayed in the order they were selected -* Defect #12356: Issue "Update" link broken focus -* Defect #12397: Error in Textile conversion of HTTP links, containing russian letters -* Defect #12434: Respond with 404 instead of 500 when requesting a wiki diff with invalid versions -* Feature #1554: Private comments in tickets -* Feature #2161: Time tracking code should respect weekends as "no work" days -* Feature #3239: Show related issues on the Issues Listing -* Feature #3265: Filter on issue relations -* Feature #3447: Option to display the issue descriptions on the issues list -* Feature #3511: Ability to sort issues by grouped column -* Feature #4590: Precede-Follow relation should move following issues when rescheduling issue earlier -* Feature #5487: Allow subtasks to cross projects -* Feature #6899: Add a relation between the original and copied issue -* Feature #7082: Rest API for wiki -* Feature #9835: REST API - List priorities -* Feature #10789: Macros {{child_pages}} with depth parameter -* Feature #10852: Ability to delete a version from a wiki page history -* Feature #10937: new user format #{lastname} -* Feature #11502: Expose roles details via REST API -* Feature #11755: Impersonate user through REST API auth -* Feature #12085: New user name format: firstname + first letter of lastname -* Feature #12125: Set filename used to store attachment updloaded via the REST API -* Feature #12167: Macro for inserting collapsible block of text -* Feature #12211: Wrap issue description and its contextual menu in a div -* Feature #12216: Textual CSS class for priorities -* Feature #12299: Redmine version requirement improvements (in plugins) -* Feature #12393: Upgrade to Rails 3.2.9 -* Feature #12475: Lazy loading of translation files for faster startup -* Patch #11846: Fill username when authentification failed -* Patch #11862: Add "last 2 weeks" preset to time entries reporting -* Patch #11992: Japanese translation about issue relations improved -* Patch #12027: Incorrect Spanish "September" month name -* Patch #12061: Japanese translation improvement (permission names) -* Patch #12078: User#allowed_to? should return true or false -* Patch #12117: Change Japanese translation of "admin" -* Patch #12142: Updated translation in Lithuanian -* Patch #12232: German translation enhancements -* Patch #12316: Fix Lithuanian numeral translation -* Patch #12494: Bulgarian "button_submit" translation change -* Patch #12514: Updated translation in Lithuanian -* Patch #12602: Korean translation update for 2.2-stable -* Patch #12608: Norwegian translation changed -* Patch #12619: Russian translation change - -== 2012-12-18 v2.1.5 - -* Defect #12400: Validation fails when receiving an email with list custom fields -* Defect #12451: Macros.rb extract_macro_options should use lazy search -* Defect #12513: Grouping of issues by custom fields not correct in PDF export -* Defect #12566: Issue history notes previews are broken -* Defect #12568: Clicking "edit" on a journal multiple times shows multiple forms -* Patch #12605: Norwegian translation for 1.4-stable update -* Patch #12614: Dutch translation -* Patch #12615: Russian translation - -== 2012-11-24 v2.1.4 - -* Defect #12274: Wiki export from Index by title is truncated -* Defect #12298: Right-click context menu unable to batch/bulk update (IE8) -* Defect #12332: Repository identifier does not display on Project/Settings/Repositories -* Defect #12396: Error when receiving an email without subject header -* Defect #12399: Non ASCII attachment filename encoding broken (MOJIBAKE) in receiving mail on Ruby 1.8 -* Defect #12409: Git: changesets aren't read after clear_changesets call -* Defect #12431: Project.rebuild! sorts root projects by id instead of name - -== 2012-11-17 v2.1.3 - -* Defect #12050: :export links to repository files lead to a 404 error -* Defect #12189: Missing tmp/pdf directory -* Defect #12195: Javascript error with IE7 / IE8 on new issue form -* Defect #12196: "Page not found" on OK button in SCM "View all revisions" page -* Defect #12199: Confirmation message displayed when clicking a disabled delete link in the context menu -* Defect #12231: Hardcoded "Back" in Repository -* Defect #12294: Incorrect german translation for "registered" users filter -* Defect #12349: Watchers auto-complete search on non-latin chars -* Defect #12358: 'None' grouped issue list section should be translated -* Defect #12359: Version date field regex validation accepts invalid date -* Defect #12375: Receiving mail subject encoding broken (MOJIBAKE) in some cases on Ruby 1.8 -* Patch #9732: German translations -* Patch #12021: Russian locale translations -* Patch #12188: Simplified Chinese translation with zh.yml file based on Rev:10681 -* Patch #12235: German translation for 2.1-stable -* Patch #12237: Added German Translation - -== 2012-09-30 v2.1.2 - -* Defect #11929: XSS vulnerability in Redmine 2.1.x - -== 2012-09-30 v2.1.1 - -* Defect #11290: ParseDate missing in Ruby 1.9x -* Defect #11844: "load_default_data" rake task fails to print the error message if one occurs -* Defect #11850: Can't create a user from ldap by on-the-fly on the redmine server using URI prefix -* Defect #11872: Private issue visible to anonymous users after its author is deleted -* Defect #11885: Filter misses Selectionfield on IE8 -* Defect #11893: New relation form Cancel link is broken with Chrome 21 -* Defect #11905: Potential "can't dup NilClass" error in UserPreference -* Defect #11909: Autocomplete results not reset after clearing search field -* Defect #11922: bs.yml and de.yml lead to error by number_to_currency() -* Defect #11945: rake task prints "can't convert Errno::EACCES into String" in case of no permission of public/plugin_assets -* Defect #11975: Undefined status transitions allowed in workflow (author of issue changes when selecting a new status) -* Defect #11982: SCM diff view generates extra parameter for switching mode -* Patch #11897: Traditional Chinese language file (to r10433) - -== 2012-09-16 v2.1.0 - -* Defect #2071: Reordering priority-enumerations breaks alternate-theme's issue-colouring -* Defect #2190: Month names not translated to german -* Defect #8978: LDAP timeout if an LDAP auth provider is unreachable -* Defect #9839: Gantt abbr of weekday should not be necessarily the first letter of the long day name -* Defect #10928: Documentation about generating a plugin is not up-to-date -* Defect #11034: TLS configuration documentation for Rails 3 -* Defect #11073: UserCustomField order_statement returns wrong output -* Defect #11153: Default sorting for target version is DESC instead of ASC -* Defect #11207: Issues associated with a locked version are not copied when copying a project -* Defect #11304: Issue-class: status-1, status-2 etc. refer to status position instead of status id -* Defect #11331: Openid registration form should not require user to enter password -* Defect #11345: Context menu should show shared versions when editing issues from different projects -* Defect #11355: Plain text notification emails content is HTML escaped -* Defect #11388: Updating a version through rest API returns invalid JSON -* Defect #11389: Warning in awesome_nested_set.rb -* Defect #11503: Accessing /projects/:project/wiki/something.png fails with error 500 -* Defect #11506: Versions that are not shared should not be assignable when selecting another project -* Defect #11508: Projects not ordered alphabetically after renaming project -* Defect #11540: Roadmap anchor links can be ambigous -* Defect #11545: Overwriting existing method Issue.open -* Defect #11552: MailHandler does not match assignee name with spaces -* Defect #11571: Custom fields of type version not proper handled in receiving e-mails -* Defect #11577: Can't use non-latin anchor in wiki -* Defect #11612: Revision graph sometimes broken due to raphael.js error -* Defect #11621: Redmine MIME Detection Of Javascript Files Non-Standard -* Defect #11633: Macro arguments should not be parsed by text formatters -* Defect #11662: Invalid query returned from Issues.visible scope after accessing User#projects_by_role with a role that is not present -* Defect #11691: 404 response when deleting a user from the edit page -* Defect #11723: redmine:send_reminders notification misses if assignee is a group -* Defect #11738: Batch update of issues clears project path -* Defect #11749: Redmine.pm: HEAD is not considered as a read-only method -* Defect #11814: Date picker does not respect week start setting -* Feature #703: Configurable required fields per tracker/status/role -* Feature #1006: Display thumbnails of attached images -* Feature #1091: Disabling default ticket fields per tracker -* Feature #1360: Permission for adding an issue to a version. -* Feature #3061: Let macros optionally match over multiple lines and ignore single curly braces -* Feature #3510: Inserting image thumbnails inside the wiki -* Feature #3521: Permissions for roles to change fields per tracker/status -* Feature #3640: Freeze / Close Projects -* Feature #3831: Support for subforums -* Feature #6597: Configurable session lifetime and timeout -* Feature #6965: Option to Copy Subtasks when copying an issue -* Feature #8161: Ability to filter issues on project custom fields -* Feature #8577: "Private" column and filter on the issue list -* Feature #8981: REST Api for Groups -* Feature #9258: Create role by copy -* Feature #9419: Group/sort the issue list by user/version-format custom fields -* Feature #10362: Show images in repositories inline when clicking the 'View' link -* Feature #10419: Upgrade raphael.js (2.1.0) -* Feature #11068: Ability to set default column order in issue list -* Feature #11102: Add autocomplete to "Related issue" field on revision -* Feature #11109: Repository Identifier should be frozen -* Feature #11181: Additional "Log time" link on project overview -* Feature #11205: Reversed order of priorities on the issue summary page -* Feature #11445: Switch from Prototype to JQuery -* Feature #11469: JSONP support -* Feature #11475: Redmine.pm: Allow fallback to other Apache auth providers -* Feature #11494: Don't turn #nnn with leading zeros into links -* Feature #11539: Display a projects tree instead of a flat list in notification preferences -* Feature #11578: Option to pass whole arguments to a macro without splitting them -* Feature #11595: Missing mime type for svg files -* Feature #11758: Upgrade to Rails 3.2.8 -* Patch #4905: Redmine.pm: add support for Git's smart HTTP protocol -* Patch #10988: New Korean translation patch -* Patch #11201: Korean translation special update -* Patch #11401: Fix Japanese mistranslation for "button_submit" -* Patch #11402: Japanese translation added for default role names -* Patch #11411: Fix disordered use of long sound in Japanese "user" translation -* Patch #11412: Unnatural Japanese message when users failed to login -* Patch #11419: Fix wrong Japanese "label_attachment" translation -* Patch #11496: Make labels clickable in Adminstration/Settings -* Patch #11704: Avoid the use of tag("...", "...", true) in layout -* Patch #11818: Redmine.pm fails when permissions are NULL - -== 2012-09-16 v2.0.4 - -* Defect #10818: Running rake in test environment causes exception -* Defect #11209: Wiki diff may generate broken HTML -* Defect #11217: Project names in drop-down are escaped twice -* Defect #11262: Link is escaped in wiki added/updated notification email -* Defect #11307: Can't filter for negative numeric custom fields -* Defect #11325: Unified diff link broken on specific file/revision diff view -* Defect #11341: Escaped link in conflict resolution form -* Defect #11365: Attachment description length is not validated -* Defect #11511: Confirmation page has broken HTML when a project folding sub project is deleted -* Defect #11533: rake redmine:plugins:test doesn't run tests in subdirectories -* Defect #11541: Version sharing is missing in the REST API -* Defect #11550: Issue reminder doesn't work when using asynchronous delivery -* Defect #11776: Can't override mailer views inside redmine plugin. -* Defect #11789: Edit section links broken with h5/h6 headings -* Feature #11338: Exclude emails with auto-submitted => auto-generated -* Patch #11299: redmine:plugins:migrate should update db/schema.rb -* Patch #11328: Fix Japanese mistranslation for 'label_language_based' -* Patch #11448: Russian translation for 1.4-stable and 2.0-stable -* Patch #11600: Fix plural form of the abbreviation for hours in Brazilian Portuguese - -== 2012-06-18 v2.0.3 - -* Defect #10688: PDF export from Wiki - Problems with tables -* Defect #11061: Cannot choose commit versions to view differences in Git/Mercurial repository view -* Defect #11065: E-Mail submitted tickets: German umlauts in 'Subject' get malformed (ruby 1.8) -* Defect #11098: Default priorities have the same position and can't be reordered -* Defect #11105: <% content_for :header_tags do %> doesn't work inside hook -* Defect #11112: REST API - custom fields in POST/PUT ignored for time_entries -* Defect #11118: "Maximum file size" displayed on upload forms is incorrect -* Defect #11124: Link to user is escaped in activity title -* Defect #11133: Wiki-page section edit link can point to incorrect section -* Defect #11160: SQL Error on time report if a custom field has multiple values for an entry -* Defect #11170: Topics sort order is broken in Redmine 2.x -* Defect #11178: Spent time sorted by date-descending order lists same-date entries in physical order (not-reverse) -* Defect #11185: Redmine fails to delete a project with parent/child task -* Feature #11162: Upgrade to Rails 3.2.6 -* Patch #11113: Small glitch in German localization - -== 2012-06-05 v2.0.2 - -* Defect #11032: Project list is not shown when "For any event on the selected projects only..." is selected on user edit panel -* Defect #11038: "Create and continue" should preserve project, issue and activity when logging time -* Defect #11046: Redmine.pm does not support "bind as user" ldap authentication -* Defect #11051: reposman.rb fails in 1.4.2 because of missing require for rubygems -* Defect #11085: Wiki start page can't be changed -* Feature #11084: Update Rails to 3.2.5 - -== 2012-05-28 v2.0.1 - -* Defect #10923: After creating a new Version Redmine jumps back to "Information" -* Defect #10932: Links to delete watchers are escaped when gravatars are enabled -* Defect #10964: Updated column doesn't get updated on issues -* Defect #10965: rake yard does not work for generating documentation. -* Defect #10972: Columns selection not displayed on the custom query form -* Defect #10991: My page > Spent time 'project' column is html-encoded -* Defect #10996: Time zones lost when upgrading from Redmine 1.4 to 2.0 -* Defect #11013: Fetching Email from IMAP/POP3 - uninitialized constant RAILS_DEFAULT_LOGGER error -* Defect #11024: redmine_plugin_model generator does not create the migration -* Defect #11027: Saving new query without name causes escaping of input field -* Defect #11028: Project identifier can be updated - -== 2012-05-15 v2.0.0 - -* Feature #4796: Rails 3 support -* Feature #7720: Limit the pagination-limit when max-results is fewer than max-pagination-limit -* Feature #9034: Add an id to the flash messages -* Patch #10782: Better translation for Estonian language - -== 2012-05-13 v1.4.2 - -* Defect #10744: rake task redmine:email:test broken -* Defect #10787: "Allow users to unsubscribe" option is confusing -* Defect #10827: Cannot access Repositories page and Settings in a Project - Error 500 -* Defect #10829: db:migrate fails 0.8.2 -> 1.4.1 -* Defect #10832: REST Uploads fail with fastcgi -* Defect #10837: reposman and rdm-mailhandler not working with ruby 1.9.x -* Defect #10856: can not load translations from hr.yml with ruby1.9.3-p194 -* Defect #10865: Filter reset when deleting locked user -* Feature #9790: Allow filtering text custom fields on "is null" and "is not null" -* Feature #10778: svn:ignore for config/additional_environment.rb -* Feature #10875: Partial Albanian Translations -* Feature #10888: Bring back List-Id to help aid Gmail filtering -* Patch #10733: Traditional Chinese language file (to r9502) -* Patch #10745: Japanese translation update (r9519) -* Patch #10750: Swedish Translation for r9522 -* Patch #10785: Bulgarian translation (jstoolbar) -* Patch #10800: Simplified Chinese translation - -== 2012-04-20 v1.4.1 - -* Defect #8574: Time report: date range fields not enabled when using the calendar popup -* Defect #10642: Nested textile ol/ul lists generate invalid HTML -* Defect #10668: RSS key is generated twice when user is not reloaded -* Defect #10669: Token.destroy_expired should not delete API tokens -* Defect #10675: "Submit and continue" is broken -* Defect #10711: User cannot change account details with "Login has already been taken" error -* Feature #10664: Unsubscribe Own User Account -* Patch #10693: German Translation Update - -== 2012-04-14 v1.4.0 - -* Defect #2719: Increase username length limit from 30 to 60 -* Defect #3087: Revision referring to issues across all projects -* Defect #4824: Unable to connect (can't convert Net::LDAP::LdapError into String) -* Defect #5058: reminder mails are not sent when delivery_method is :async_smtp -* Defect #6859: Moving issues to a tracker with different custom fields should let fill these fields -* Defect #7398: Error when trying to quick create a version with required custom field -* Defect #7495: Python multiline comments highlighting problem in Repository browser -* Defect #7826: bigdecimal-segfault-fix.rb must be removed for Oracle -* Defect #7920: Attempted to update a stale object when copying a project -* Defect #8857: Git: Too long in fetching repositories after upgrade from 1.1 or new branch at first time -* Defect #9472: The git scm module causes an excess amount of DB traffic. -* Defect #9685: Adding multiple times the same related issue relation is possible -* Defect #9798: Release 1.3.0 does not detect rubytree under ruby 1.9.3p0 / rails 2.3.14 -* Defect #9978: Japanese "permission_add_issue_watchers" is wrong -* Defect #10006: Email reminders are sent for closed issues -* Defect #10150: CSV export and spent time: rounding issue -* Defect #10168: CSV export breaks custom columns -* Defect #10181: Issue context menu and bulk edit form show irrelevant statuses -* Defect #10198: message_id regex in pop3.rb only recognizes Message-ID header (not Message-Id) -* Defect #10251: Description diff link in note details is relative when received by email -* Defect #10272: Ruby 1.9.3: "incompatible character encoding" with LDAP auth -* Defect #10275: Message object not passed to wiki macros for head topic and in preview edit mode -* Defect #10334: Full name is not unquoted when creating users from emails -* Defect #10410: [Localization] Grammar issue of Simplified Chinese in zh.yml -* Defect #10442: Ruby 1.9.3 Time Zone setting Internal error. -* Defect #10467: Confusing behavior while moving issue to a project with disabled Issues module -* Defect #10575: Uploading of attachments which filename contains non-ASCII chars fails with Ruby 1.9 -* Defect #10590: WikiContent::Version#text return string with # when uncompressed -* Defect #10593: Error: 'incompatible character encodings: UTF-8 and ASCII-8BIT' (old annoing issue) on ruby-1.9.3 -* Defect #10600: Watchers search generates an Internal error -* Defect #10605: Bulk edit selected issues does not allow selection of blank values for custom fields -* Defect #10619: When changing status before tracker, it shows improper status -* Feature #779: Multiple SCM per project -* Feature #971: Add "Spent time" column to query -* Feature #1060: Add a LDAP-filter using external auth sources -* Feature #1102: Shortcut for assigning an issue to me -* Feature #1189: Multiselect custom fields -* Feature #1363: Allow underscores in project identifiers -* Feature #1913: LDAP - authenticate as user -* Feature #1972: Attachments for News -* Feature #2009: Manually add related revisions -* Feature #2323: Workflow permissions for administrators -* Feature #2416: {background:color} doesn't work in text formatting -* Feature #2694: Notification on loosing assignment -* Feature #2715: "Magic links" to notes -* Feature #2850: Add next/previous navigation to issue -* Feature #3055: Option to copy attachments when copying an issue -* Feature #3108: set parent automatically for new pages -* Feature #3463: Export all wiki pages to PDF -* Feature #4050: Ruby 1.9 support -* Feature #4769: Ability to move an issue to a different project from the update form -* Feature #4774: Change the hyperlink for file attachment to view and download -* Feature #5159: Ability to add Non-Member watchers to the watch list -* Feature #5638: Use Bundler (Gemfile) for gem management -* Feature #5643: Add X-Redmine-Sender header to email notifications -* Feature #6296: Bulk-edit custom fields through context menu -* Feature #6386: Issue mail should render the HTML version of the issue details -* Feature #6449: Edit a wiki page's parent on the edit page -* Feature #6555: Double-click on "Submit" and "Save" buttons should not send two requests to server -* Feature #7361: Highlight active query in the side bar -* Feature #7420: Rest API for projects members -* Feature #7603: Please make editing issues more obvious than "Change properties (More)" -* Feature #8171: Adding attachments through the REST API -* Feature #8691: Better handling of issue update conflict -* Feature #9803: Change project through REST API issue update -* Feature #9923: User type custom fields should be filterable by "Me". -* Feature #9985: Group time report by the Status field -* Feature #9995: Time entries insertion, "Create and continue" button -* Feature #10020: Enable global time logging at /time_entries/new -* Feature #10042: Bulk change private flag -* Feature #10126: Add members of subprojects in the assignee and author filters -* Feature #10131: Include custom fiels in time entries API responses -* Feature #10207: Git: use default branch from HEAD -* Feature #10208: Estonian translation -* Feature #10253: Better handling of attachments when validation fails -* Feature #10350: Bulk copy should allow for changing the target version -* Feature #10607: Ignore out-of-office incoming emails -* Feature #10635: Adding time like "123 Min" is invalid -* Patch #9998: Make attachement "Optional Description" less wide -* Patch #10066: i18n not working with russian gem -* Patch #10128: Disable IE 8 compatibility mode to fix wrong div.autoscroll scroll bar behaviour -* Patch #10155: Russian translation changed -* Patch #10464: Enhanced PDF output for Issues list -* Patch #10470: Efficiently process new git revisions in a single batch -* Patch #10513: Dutch translation improvement - -== 2012-04-14 v1.3.3 - -* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass) -* Defect #10554: Defect symbols when exporting tasks in pdf -* Defect #10564: Unable to change locked, sticky flags and board when editing a message -* Defect #10591: Dutch "label_file_added" translation is wrong -* Defect #10622: "Default administrator account changed" is always true -* Patch #10555: rake redmine:send_reminders aborted if issue assigned to group -* Patch #10611: Simplified Chinese translations for 1.3-stable - -== 2012-03-11 v1.3.2 - -* Defect #8194: {{toc}} uses identical anchors for subsections with the same name -* Defect #9143: Partial diff comparison should be done on actual code, not on html -* Defect #9523: {{toc}} does not display headers with @ code markup -* Defect #9815: Release 1.3.0 does not detect rubytree with rubgems 1.8 -* Defect #10053: undefined method `<=>' for nil:NilClass when accessing the settings of a project -* Defect #10135: ActionView::TemplateError (can't convert Fixnum into String) -* Defect #10193: Unappropriate icons in highlighted code block -* Defect #10199: No wiki section edit when title contains code -* Defect #10218: Error when creating a project with a version custom field -* Defect #10241: "get version by ID" fails with "401 not authorized" error when using API access key -* Defect #10284: Note added by commit from a subproject does not contain project identifier -* Defect #10374: User list is empty when adding users to project / group if remaining users are added late -* Defect #10390: Mass assignment security vulnerability -* Patch #8413: Confirmation message before deleting a relationship -* Patch #10160: Bulgarian translation (r8777) -* Patch #10242: Migrate Redmine.pm from Digest::Sha1 to Digest::Sha -* Patch #10258: Italian translation for 1.3-stable - -== 2012-02-06 v1.3.1 - -* Defect #9775: app/views/repository/_revision_graph.html.erb sets window.onload directly.. -* Defect #9792: Ruby 1.9: [v1.3.0] Error: incompatible character encodings for it translation on Calendar page -* Defect #9793: Bad spacing between numbered list and heading (recently broken). -* Defect #9795: Unrelated error message when creating a group with an invalid name -* Defect #9832: Revision graph height should depend on height of rows in revisions table -* Defect #9937: Repository settings are not saved when all SCM are disabled -* Defect #9961: Ukrainian "default_tracker_bug" is wrong -* Defect #10013: Rest API - Create Version -> Internal server error 500 -* Defect #10115: Javascript error - Can't attach more than 1 file on IE 6 and 7 -* Defect #10130: Broken italic text style in edited comment preview -* Defect #10152: Attachment diff type is not saved in user preference -* Feature #9943: Arabic translation -* Patch #9874: pt-BR translation updates -* Patch #9922: Spanish translation updated -* Patch #10137: Korean language file ko.yml updated to Redmine 1.3.0 - -== 2011-12-10 v1.3.0 - -* Defect #2109: Context menu is being submitted twice per right click -* Defect #7717: MailHandler user creation for unknown_user impossible due to diverging length-limits of login and email fields -* Defect #7917: Creating users via email fails if user real name containes special chars -* Defect #7966: MailHandler does not include JournalDetail for attached files -* Defect #8368: Bad decimal separator in time entry CSV -* Defect #8371: MySQL error when filtering a custom field using the REST api -* Defect #8549: Export CSV has character encoding error -* Defect #8573: Do not show inactive Enumerations where not needed -* Defect #8611: rake/rdoctask is deprecated -* Defect #8751: Email notification: bug, when number of recipients more then 8 -* Defect #8894: Private issues - make it more obvious in the UI? -* Defect #8994: Hardcoded French string "anonyme" -* Defect #9043: Hardcoded string "diff" in Wiki#show and Repositories_Helper -* Defect #9051: wrong "text_issue_added" in russian translation. -* Defect #9108: Custom query not saving status filter -* Defect #9252: Regression: application title escaped 2 times -* Defect #9264: Bad Portuguese translation -* Defect #9470: News list is missing Avatars -* Defect #9471: Inline markup broken in Wiki link labels -* Defect #9489: Label all input field and control tags -* Defect #9534: Precedence: bulk email header is non standard and discouraged -* Defect #9540: Issue filter by assigned_to_role is not project specific -* Defect #9619: Time zone ignored when logging time while editing ticket -* Defect #9638: Inconsistent image filename extensions -* Defect #9669: Issue list doesn't sort assignees/authors regarding user display format -* Defect #9672: Message-quoting in forums module broken -* Defect #9719: Filtering by numeric custom field types broken after update to master -* Defect #9724: Can't remote add new categories -* Defect #9738: Setting of cross-project custom query is not remembered inside project -* Defect #9748: Error about configuration.yml validness should mention file path -* Feature #69: Textilized description in PDF -* Feature #401: Add pdf export for WIKI page -* Feature #1567: Make author column sortable and groupable -* Feature #2222: Single section edit. -* Feature #2269: Default issue start date should become configurable. -* Feature #2371: character encoding for attachment file -* Feature #2964: Ability to assign issues to groups -* Feature #3033: Bug Reporting: Using "Create and continue" should show bug id of saved bug -* Feature #3261: support attachment images in PDF export -* Feature #4264: Update CodeRay to 1.0 final -* Feature #4324: Redmine renames my files, it shouldn't. -* Feature #4729: Add Date-Based Filters for Issues List -* Feature #4742: CSV export: option to export selected or all columns -* Feature #4976: Allow rdm-mailhandler to read the API key from a file -* Feature #5501: Git: Mercurial: Adding visual merge/branch history to repository view -* Feature #5634: Export issue to PDF does not include Subtasks and Related Issues -* Feature #5670: Cancel option for file upload -* Feature #5737: Custom Queries available through the REST Api -* Feature #6180: Searchable custom fields do not provide adequate operators -* Feature #6954: Filter from date to date -* Feature #7180: List of statuses in REST API -* Feature #7181: List of trackers in REST API -* Feature #7366: REST API for Issue Relations -* Feature #7403: REST API for Versions -* Feature #7671: REST API for reading attachments -* Feature #7832: Ability to assign issue categories to groups -* Feature #8420: Consider removing #7013 workaround -* Feature #9196: Improve logging in MailHandler when user creation fails -* Feature #9496: Adds an option in mailhandler to disable server certificate verification -* Feature #9553: CRUD operations for "Issue categories" in REST API -* Feature #9593: HTML title should be reordered -* Feature #9600: Wiki links for news and forums -* Feature #9607: Filter for issues without start date (or any another field based on date type) -* Feature #9609: Upgrade to Rails 2.3.14 -* Feature #9612: "side by side" and "inline" patch view for attachments -* Feature #9667: Check attachment size before upload -* Feature #9690: Link in notification pointing to the actual update -* Feature #9720: Add note number for single issue's PDF -* Patch #8617: Indent subject of subtask ticket in exported issues PDF -* Patch #8778: Traditional Chinese 'issue' translation change -* Patch #9053: Fix up Russian translation -* Patch #9129: Improve wording of Git repository note at project setting -* Patch #9148: Better handling of field_due_date italian translation -* Patch #9273: Fix typos in russian localization -* Patch #9484: Limit SCM annotate to text files under the maximum file size for viewing -* Patch #9659: Indexing rows in auth_sources/index view -* Patch #9692: Fix Textilized description in PDF for CodeRay - -== 2011-12-10 v1.2.3 - -* Defect #8707: Reposman: wrong constant name -* Defect #8809: Table in timelog report overflows -* Defect #9055: Version files in Files module cannot be downloaded if issue tracking is disabled -* Defect #9137: db:encrypt fails to handle repositories with blank password -* Defect #9394: Custom date field only validating on regex and not a valid date -* Defect #9405: Any user with :log_time permission can edit time entries via context menu -* Defect #9448: The attached images are not shown in documents -* Defect #9520: Copied private query not visible after project copy -* Defect #9552: Error when reading ciphered text from the database without cipher key configured -* Defect #9566: Redmine.pm considers all projects private when login_required is enabled -* Defect #9567: Redmine.pm potential security issue with cache credential enabled and subversion -* Defect #9577: Deleting a subtasks doesn't update parent's rgt & lft values -* Defect #9597: Broken version links in wiki annotate history -* Defect #9682: Wiki HTML Export only useful when Access history is accessible -* Defect #9737: Custom values deleted before issue submit -* Defect #9741: calendar-hr.js (Croatian) is not UTF-8 -* Patch #9558: Simplified Chinese translation for 1.2.2 updated -* Patch #9695: Bulgarian translation (r7942) - -== 2011-11-11 v1.2.2 - -* Defect #3276: Incorrect handling of anchors in Wiki to HTML export -* Defect #7215: Wiki formatting mangles links to internal headers -* Defect #7613: Generated test instances may share the same attribute value object -* Defect #8411: Can't remove "Project" column on custom query -* Defect #8615: Custom 'version' fields don't show shared versions -* Defect #8633: Pagination counts non visible issues -* Defect #8651: Email attachments are not added to issues any more in v1.2 -* Defect #8825: JRuby + Windows: SCMs do not work on Redmine 1.2 -* Defect #8836: Additional workflow transitions not available when set to both author and assignee -* Defect #8865: Custom field regular expression is not validated -* Defect #8880: Error deleting issue with grandchild -* Defect #8884: Assignee is cleared when updating issue with locked assignee -* Defect #8892: Unused fonts in rfpdf plugin folder -* Defect #9161: pt-BR field_warn_on_leaving_unsaved has a small gramatical error -* Defect #9308: Search fails when a role haven't "view wiki" permission -* Defect #9465: Mercurial: can't browse named branch below Mercurial 1.5 - -== 2011-07-11 v1.2.1 - -* Defect #5089: i18N error on truncated revision diff view -* Defect #7501: Search options get lost after clicking on a specific result type -* Defect #8229: "project.xml" response does not include the parent ID -* Defect #8449: Wiki annotated page does not display author of version 1 -* Defect #8467: Missing german translation - Warn me when leaving a page with unsaved text -* Defect #8468: No warning when leaving page with unsaved text that has not lost focus -* Defect #8472: Private checkbox ignored on issue creation with "Set own issues public or private" permission -* Defect #8510: JRuby: Can't open administrator panel if scm command is not available -* Defect #8512: Syntax highlighter on Welcome page -* Defect #8554: Translation missing error on custom field validation -* Defect #8565: JRuby: Japanese PDF export error -* Defect #8566: Exported PDF UTF-8 Vietnamese not correct -* Defect #8569: JRuby: PDF export error with TypeError -* Defect #8576: Missing german translation - different things -* Defect #8616: Circular relations -* Defect #8646: Russian translation "label_follows" and "label_follows" are wrong -* Defect #8712: False 'Description updated' journal details messages -* Defect #8729: Not-public queries are not private -* Defect #8737: Broken line of long issue description on issue PDF. -* Defect #8738: Missing revision number/id of associated revisions on issue PDF -* Defect #8739: Workflow copy does not copy advanced workflow settings -* Defect #8759: Setting issue attributes from mail should be case-insensitive -* Defect #8777: Mercurial: Not able to Resetting Redmine project respository - -== 2011-05-30 v1.2.0 - -* Defect #61: Broken character encoding in pdf export -* Defect #1965: Redmine is not Tab Safe -* Defect #2274: Filesystem Repository path encoding of non UTF-8 characters -* Defect #2664: Mercurial: Repository path encoding of non UTF-8 characters -* Defect #3421: Mercurial reads files from working dir instead of changesets -* Defect #3462: CVS: Repository path encoding of non UTF-8 characters -* Defect #3715: Login page should not show projects link and search box if authentication is required -* Defect #3724: Mercurial repositories display revision ID instead of changeset ID -* Defect #3761: Most recent CVS revisions are missing in "revisions" view -* Defect #4270: CVS Repository view in Project doesn't show Author, Revision, Comment -* Defect #5138: Don't use Ajax for pagination -* Defect #5152: Cannot use certain characters for user and role names. -* Defect #5251: Git: Repository path encoding of non UTF-8 characters -* Defect #5373: Translation missing when adding invalid watchers -* Defect #5817: Shared versions not shown in subproject's gantt chart -* Defect #6013: git tab,browsing, very slow -- even after first time -* Defect #6148: Quoting, newlines, and nightmares... -* Defect #6256: Redmine considers non ASCII and UTF-16 text files as binary in SCM -* Defect #6476: Subproject's issues are not shown in the subproject's gantt -* Defect #6496: Remove i18n 0.3.x/0.4.x hack for Rails 2.3.5 -* Defect #6562: Context-menu deletion of issues deletes all subtasks too without explicit prompt -* Defect #6604: Issues targeted at parent project versions' are not shown on gantt chart -* Defect #6706: Resolving issues with the commit message produces the wrong comment with CVS -* Defect #6901: Copy/Move an issue does not give any history of who actually did the action. -* Defect #6905: Specific heading-content breaks CSS -* Defect #7000: Project filter not applied on versions in Gantt chart -* Defect #7097: Starting day of week cannot be set to Saturday -* Defect #7114: New gantt doesn't display some projects -* Defect #7146: Git adapter lost commits before 7 days from database latest changeset -* Defect #7218: Date range error on issue query -* Defect #7257: "Issues by" version links bad criterias -* Defect #7279: CSS class ".icon-home" is not used. -* Defect #7320: circular dependency >2 issues -* Defect #7352: Filters not working in Gantt charts -* Defect #7367: Receiving pop3 email should not output debug messages -* Defect #7373: Error with PDF output and ruby 1.9.2 -* Defect #7379: Remove extraneous hidden_field on wiki history -* Defect #7516: Redmine does not work with RubyGems 1.5.0 -* Defect #7518: Mercurial diff can be wrong if the previous changeset isn't the parent -* Defect #7581: Not including a spent time value on the main issue update screen causes silent data loss -* Defect #7582: hiding form pages from search engines -* Defect #7597: Subversion and Mercurial log have the possibility to miss encoding -* Defect #7604: ActionView::TemplateError (undefined method `name' for nil:NilClass) -* Defect #7605: Using custom queries always redirects to "Issues" tab -* Defect #7615: CVS diffs do not handle new files properly -* Defect #7618: SCM diffs do not handle one line new files properly -* Defect #7639: Some date fields do not have requested format. -* Defect #7657: Wrong commit range in git log command on Windows -* Defect #7818: Wiki pages don't use the local timezone to display the "Updated ? hours ago" mouseover -* Defect #7821: Git "previous" and "next" revisions are incorrect -* Defect #7827: CVS: Age column on repository view is off by timezone delta -* Defect #7843: Add a relation between issues = explicit login window ! (basic authentication popup is prompted on AJAX request) -* Defect #8011: {{toc}} does not display headlines with inline code markup -* Defect #8029: List of users for adding to a group may be empty if 100 first users have been added -* Defect #8064: Text custom fields do not wrap on the issue list -* Defect #8071: Watching a subtask from the context menu updates main issue watch link -* Defect #8072: Two untranslatable default role names -* Defect #8075: Some "notifiable" names are not i18n-enabled -* Defect #8081: GIT: Commits missing when user has the "decorate" git option enabled -* Defect #8088: Colorful indentation of subprojects must be on right in RTL locales -* Defect #8239: notes field is not propagated during issue copy -* Defect #8356: GET /time_entries.xml ignores limit/offset parameters -* Defect #8432: Private issues information shows up on Activity page for unauthorized users -* Feature #746: Versioned issue descriptions -* Feature #1067: Differentiate public/private saved queries in the sidebar -* Feature #1236: Make destination folder for attachment uploads configurable -* Feature #1735: Per project repository log encoding setting -* Feature #1763: Autologin-cookie should be configurable -* Feature #1981: display mercurial tags -* Feature #2074: Sending email notifications when comments are added in the news section -* Feature #2096: Custom fields referencing system tables (users and versions) -* Feature #2732: Allow additional workflow transitions for author and assignee -* Feature #2910: Warning on leaving edited issue/wiki page without saving -* Feature #3396: Git: use --encoding=UTF-8 in "git log" -* Feature #4273: SCM command availability automatic check in administration panel -* Feature #4477: Use mime types in downloading from repository -* Feature #5518: Graceful fallback for "missing translation" needed -* Feature #5520: Text format buttons and preview link missing when editing comment -* Feature #5831: Parent Task to Issue Bulk Edit -* Feature #6887: Upgrade to Rails 2.3.11 -* Feature #7139: Highlight changes inside diff lines -* Feature #7236: Collapse All for Groups -* Feature #7246: Handle "named branch" for mercurial -* Feature #7296: Ability for admin to delete users -* Feature #7318: Add user agent to Redmine Mailhandler -* Feature #7408: Add an application configuration file -* Feature #7409: Cross project Redmine links -* Feature #7410: Add salt to user passwords -* Feature #7411: Option to cipher LDAP ans SCM passwords stored in the database -* Feature #7412: Add an issue visibility level to each role -* Feature #7414: Private issues -* Feature #7517: Configurable path of executable for scm adapters -* Feature #7640: Add "mystery man" gravatar to options -* Feature #7858: RubyGems 1.6 support -* Feature #7893: Group filter on the users list -* Feature #7899: Box for editing comments should open with the formatting toolbar -* Feature #7921: issues by pulldown should have 'status' option -* Feature #7996: Bulk edit and context menu for time entries -* Feature #8006: Right click context menu for Related Issues -* Feature #8209: I18n YAML files not parsable with psych yaml library -* Feature #8345: Link to user profile from account page -* Feature #8365: Git: per project setting to report last commit or not in repository tree -* Patch #5148: metaKey not handled in issues selection -* Patch #5629: Wrap text fields properly in PDF -* Patch #7418: Redmine Persian Translation -* Patch #8295: Wrap title fields properly in PDF -* Patch #8310: fixes automatic line break problem with TCPDF -* Patch #8312: Switch to TCPDF from FPDF for PDF export - -== 2011-04-29 v1.1.3 - -* Defect #5773: Email reminders are sent to locked users -* Defect #6590: Wrong file list link in email notification on new file upload -* Defect #7589: Wiki page with backslash in title can not be found -* Defect #7785: Mailhandler keywords are not removed when updating issues -* Defect #7794: Internal server error on formatting an issue as a PDF in Japanese -* Defect #7838: Gantt- Issues does not show up in green when start and end date are the same -* Defect #7846: Headers (h1, etc.) containing backslash followed by a digit are not displayed correctly -* Defect #7875: CSV export separators in polish locale (pl.yml) -* Defect #7890: Internal server error when referencing an issue without project in commit message -* Defect #7904: Subprojects not properly deleted when deleting a parent project -* Defect #7939: Simultaneous Wiki Updates Cause Internal Error -* Defect #7951: Atom links broken on wiki index -* Defect #7954: IE 9 can not select issues, does not display context menu -* Defect #7985: Trying to do a bulk edit results in "Internal Error" -* Defect #8003: Error raised by reposman.rb under Windows server 2003 -* Defect #8012: Wrong selection of modules when adding new project after validation error -* Defect #8038: Associated Revisions OL/LI items are not styled properly in issue view -* Defect #8067: CSV exporting in Italian locale -* Defect #8235: bulk edit issues and copy issues error in es, gl and ca locales -* Defect #8244: selected modules are not activated when copying a project -* Patch #7278: Update Simplified Chinese translation to 1.1 -* Patch #7390: Fixes in Czech localization -* Patch #7963: Reminder email: Link for show all issues does not sort - -== 2011-03-07 v1.1.2 - -* Defect #3132: Bulk editing menu non-functional in Opera browser -* Defect #6090: Most binary files become corrupted when downloading from CVS repository browser when Redmine is running on a Windows server -* Defect #7280: Issues subjects wrap in Gantt -* Defect #7288: Non ASCII filename downloaded from repo is broken on Internet Explorer. -* Defect #7317: Gantt tab gives internal error due to nil avatar icon -* Defect #7497: Aptana Studio .project file added to version 1.1.1-stable -* Defect #7611: Workflow summary shows X icon for workflow with exactly 1 status transition -* Defect #7625: Syntax highlighting unavailable from board new topic or topic edit preview -* Defect #7630: Spent time in commits not recognized -* Defect #7656: MySQL SQL Syntax Error when filtering issues by Assignee's Group -* Defect #7718: Minutes logged in commit message are converted to hours -* Defect #7763: Email notification are sent to watchers even if 'No events' setting is chosen -* Feature #7608: Add "retro" gravatars -* Patch #7598: Extensible MailHandler -* Patch #7795: Internal server error at journals#index with custom fields - -== 2011-01-30 v1.1.1 - -* Defect #4899: Redmine fails to list files for darcs repository -* Defect #7245: Wiki fails to find pages with cyrillic characters using postgresql -* Defect #7256: redmine/public/.htaccess must be moved for non-fastcgi installs/upgrades -* Defect #7258: Automatic spent time logging does not work properly with SQLite3 -* Defect #7259: Released 1.1.0 uses "devel" label inside admin information -* Defect #7265: "Loading..." icon does not disappear after add project member -* Defect #7266: Test test_due_date_distance_in_words fail due to undefined locale -* Defect #7274: CSV value separator in dutch locale -* Defect #7277: Enabling gravatas causes usernames to overlap first name field in user list -* Defect #7294: "Notifiy for only project I select" is not available anymore in 1.1.0 -* Defect #7307: HTTP 500 error on query for empty revision -* Defect #7313: Label not translated in french in Settings/Email Notification tab -* Defect #7329: with long strings may hang server -* Defect #7337: My page french translation -* Defect #7348: French Translation of "Connection" -* Defect #7385: Error when viewing an issue which was related to a deleted subtask -* Defect #7386: NoMethodError on pdf export -* Defect #7415: Darcs adapter recognizes new files as modified files above Darcs 2.4 -* Defect #7421: no email sent with 'Notifiy for any event on the selected projects only' -* Feature #5344: Update to latest CodeRay 0.9.x - -== 2011-01-09 v1.1.0 - -* Defect #2038: Italics in wiki headers show-up wrong in the toc -* Defect #3449: Redmine Takes Too Long On Large Mercurial Repository -* Defect #3567: Sorting for changesets might go wrong on Mercurial repos -* Defect #3707: {{toc}} doesn't work with {{include}} -* Defect #5096: Redmine hangs up while browsing Git repository -* Defect #6000: Safe Attributes prevents plugin extension of Issue model... -* Defect #6064: Modules not assigned to projects created via API -* Defect #6110: MailHandler should allow updating Issue Priority and Custom fields -* Defect #6136: JSON API holds less information than XML API -* Defect #6345: xml used by rest API is invalid -* Defect #6348: Gantt chart PDF rendering errors -* Defect #6403: Updating an issue with custom fields fails -* Defect #6467: "Member of role", "Member of group" filter not work correctly -* Defect #6473: New gantt broken after clearing issue filters -* Defect #6541: Email notifications send to everybody -* Defect #6549: Notification settings not migrated properly -* Defect #6591: Acronyms must have a minimum of three characters -* Defect #6674: Delete time log broken after changes to REST -* Defect #6681: Mercurial, Bazaar and Darcs auto close issue text should be commit id instead of revision number -* Defect #6724: Wiki uploads does not work anymore (SVN 4266) -* Defect #6746: Wiki links are broken on Activity page -* Defect #6747: Wiki diff does not work since r4265 -* Defect #6763: New gantt charts: subject displayed twice on issues -* Defect #6826: Clicking "Add" twice creates duplicate member record -* Defect #6844: Unchecking status filter on the issue list has no effect -* Defect #6895: Wrong Polish translation of "blocks" -* Defect #6943: Migration from boolean to varchar fails on PostgreSQL 8.1 -* Defect #7064: Mercurial adapter does not recognize non alphabetic nor numeric in UTF-8 copied files -* Defect #7128: New gantt chart does not render subtasks under parent task -* Defect #7135: paging mechanism returns the same last page forever -* Defect #7188: Activity page not refreshed when changing language -* Defect #7195: Apply CLI-supplied defaults for incoming mail only to new issues not replies -* Defect #7197: Tracker reset to default when replying to an issue email -* Defect #7213: Copy project does not copy all roles and permissions -* Defect #7225: Project settings: Trackers & Custom fields only relevant if module Issue tracking is active -* Feature #630: Allow non-unique names for projects -* Feature #1738: Add a "Visible" flag to project/user custom fields -* Feature #2803: Support for Javascript in Themes -* Feature #2852: Clean Incoming Email of quoted text "----- Reply above this line ------" -* Feature #2995: Improve error message when trying to access an archived project -* Feature #3170: Autocomplete issue relations on subject -* Feature #3503: Administrator Be Able To Modify Email settings Of Users -* Feature #4155: Automatic spent time logging from commit messages -* Feature #5136: Parent select on Wiki rename page -* Feature #5338: Descendants (subtasks) should be available via REST API -* Feature #5494: Wiki TOC should display heading from level 4 -* Feature #5594: Improve MailHandler's keyword handling -* Feature #5622: Allow version to be set via incoming email -* Feature #5712: Reload themes -* Feature #5869: Issue filters by Group and Role -* Feature #6092: Truncate Git revision labels in Activity page/feed and allow configurable length -* Feature #6112: Accept localized keywords when receiving emails -* Feature #6140: REST issues response with issue count limit and offset -* Feature #6260: REST API for Users -* Feature #6276: Gantt Chart rewrite -* Feature #6446: Remove length limits on project identifier and name -* Feature #6628: Improvements in truncate email -* Feature #6779: Project JSON API -* Feature #6823: REST API for time tracker. -* Feature #7072: REST API for news -* Feature #7111: Expose more detail on journal entries -* Feature #7141: REST API: get information about current user -* Patch #4807: Allow to set the done_ratio field with the incoming mail system -* Patch #5441: Initialize TimeEntry attributes with params[:time_entry] -* Patch #6762: Use GET instead of POST to retrieve context_menu -* Patch #7160: French translation ofr "not_a_date" is missing -* Patch #7212: Missing remove_index in AddUniqueIndexOnMembers down migration - - -== 2010-12-23 v1.0.5 - -* #6656: Mercurial adapter loses seconds of commit times -* #6996: Migration trac(sqlite3) -> redmine(postgresql) doesnt escape ' char -* #7013: v-1.0.4 trunk - see {{count}} in page display rather than value -* #7016: redundant 'field_start_date' in ja.yml -* #7018: 'undefined method `reschedule_after' for nil:NilClass' on new issues -* #7024: E-mail notifications about Wiki changes. -* #7033: 'class' attribute of
     tag shouldn't be truncate
    -* #7035: CSV value separator in russian
    -* #7122: Issue-description Quote-button missing
    -* #7144: custom queries making use of deleted custom fields cause a 500 error
    -* #7162: Multiply defined label in french translation
    -
    -== 2010-11-28 v1.0.4
    -
    -* #5324: Git not working if color.ui is enabled
    -* #6447: Issues API doesn't allow full key auth for all actions
    -* #6457: Edit User group problem
    -* #6575: start date being filled with current date even when blank value is submitted
    -* #6740: Max attachment size, incorrect usage of 'KB'
    -* #6760: Select box sorted by ID instead of name in Issue Category
    -* #6766: Changing target version name can cause an internal error
    -* #6784: Redmine not working with i18n gem 0.4.2
    -* #6839: Hardcoded absolute links in my/page_layout
    -* #6841: Projects API doesn't allow full key auth for all actions
    -* #6860: svn: Write error: Broken pipe when browsing repository
    -* #6874: API should return XML description when creating a project
    -* #6932: submitting wrong parent task input creates a 500 error
    -* #6966: Records of Forums are remained, deleting project
    -* #6990: Layout problem in workflow overview
    -* #5117: mercurial_adapter should ensure the right LANG environment variable
    -* #6782: Traditional Chinese language file (to r4352)
    -* #6783: Swedish Translation for r4352
    -* #6804: Bugfix: spelling fixes
    -* #6814: Japanese Translation for r4362
    -* #6948: Bulgarian translation
    -* #6973: Update es.yml
    -
    -== 2010-10-31 v1.0.3
    -
    -* #4065: Redmine.pm doesn't work with LDAPS and a non-standard port
    -* #4416: Link from version details page to edit the wiki.
    -* #5484: Add new issue as subtask to an existing ticket
    -* #5948: Update help/wiki_syntax_detailed.html with more link options
    -* #6494: Typo in pt_BR translation for 1.0.2
    -* #6508: Japanese translation update
    -* #6509: Localization pt-PT (new strings)
    -* #6511: Rake task to test email
    -* #6525: Traditional Chinese language file (to r4225)
    -* #6536: Patch for swedish translation
    -* #6548: Rake tasks to add/remove i18n strings
    -* #6569: Updated Hebrew translation
    -* #6570: Japanese Translation for r4231
    -* #6596: pt-BR translation updates
    -* #6629: Change field-name of issues start date
    -* #6669: Bulgarian translation
    -* #6731: Macedonian translation fix
    -* #6732: Japanese Translation for r4287
    -* #6735: Add user-agent to reposman
    -* #6736: Traditional Chinese language file (to r4288)
    -* #6739: Swedish Translation for r4288
    -* #6765: Traditional Chinese language file (to r4302)
    -* Fixed #5324: Git not working if color.ui is enabled
    -* Fixed #5652: Bad URL parsing in the wiki when it ends with right-angle-bracket(greater-than mark).
    -* Fixed #5803: Precedes/Follows Relationships Broke
    -* Fixed #6435: Links to wikipages bound to versions do not respect version-sharing in Settings -> Versions
    -* Fixed #6438: Autologin cannot be disabled again once it's enabled
    -* Fixed #6513: "Move" and "Copy" are not displayed when deployed in subdirectory
    -* Fixed #6521: Tooltip/label for user "search-refinment" field on group/project member list
    -* Fixed #6563: i18n-issues on calendar view
    -* Fixed #6598: Wrong caption for button_create_and_continue in German language file
    -* Fixed #6607: Unclear caption for german button_update
    -* Fixed #6612: SortHelper missing from CalendarsController
    -* Fixed #6740: Max attachment size, incorrect usage of 'KB'
    -* Fixed #6750: ActionView::TemplateError (undefined method `empty?' for nil:NilClass) on line #12 of app/views/context_menus/issues.html.erb:
    -
    -== 2010-09-26 v1.0.2
    -
    -* #2285: issue-refinement: pressing enter should result to an "apply"
    -* #3411: Allow mass status update trough context menu
    -* #5929: https-enabled gravatars when called over https
    -* #6189: Japanese Translation for r4011
    -* #6197: Traditional Chinese language file (to r4036)
    -* #6198: Updated german translation
    -* #6208: Macedonian translation
    -* #6210: Swedish Translation for r4039
    -* #6248: nl translation update for r4050
    -* #6263: Catalan translation update
    -* #6275: After submitting a related issue, the Issue field should be re-focused
    -* #6289: Checkboxes in issues list shouldn't be displayed when printing
    -* #6290: Make journals theming easier
    -* #6291: User#allowed_to? is not tested
    -* #6306: Traditional Chinese language file (to r4061)
    -* #6307: Korean translation update for 4066(4061)
    -* #6316: pt_BR update
    -* #6339: SERBIAN Updated
    -* #6358: Updated Polish translation
    -* #6363: Japanese Translation for r4080
    -* #6365: Traditional Chinese language file (to r4081)
    -* #6382: Issue PDF export variable usage
    -* #6428: Interim solution for i18n >= 0.4
    -* #6441: Japanese Translation for r4162
    -* #6451: Traditional Chinese language file (to r4167)
    -* #6465: Japanese Translation for r4171
    -* #6466: Traditional Chinese language file (to r4171)
    -* #6490: pt-BR translation for 1.0.2
    -* Fixed #3935: stylesheet_link_tag with plugin doesn't take into account relative_url_root
    -* Fixed #4998: Global issue list's context menu has enabled options for parent menus but there are no valid selections
    -* Fixed #5170: Done ratio can not revert to 0% if status is used for done ratio
    -* Fixed #5608: broken with i18n 0.4.0
    -* Fixed #6054: Error 500 on filenames with whitespace in git reposities
    -* Fixed #6135: Default logger configuration grows without bound.
    -* Fixed #6191: Deletion of a main task deletes all subtasks
    -* Fixed #6195: Missing move issues between projects
    -* Fixed #6242: can't switch between inline and side-by-side diff
    -* Fixed #6249: Create and continue returns 404
    -* Fixed #6267: changing the authentication mode from ldap to internal with setting the password
    -* Fixed #6270: diff coderay malformed in the "news" page
    -* Fixed #6278: missing "cant_link_an_issue_with_a_descendant"from locale files
    -* Fixed #6333: Create and continue results in a 404 Error
    -* Fixed #6346: Age column on repository view is skewed for git, probably CVS too
    -* Fixed #6351: Context menu on roadmap broken
    -* Fixed #6388: New Subproject leads to a 404
    -* Fixed #6392: Updated/Created links to activity broken
    -* Fixed #6413: Error in SQL
    -* Fixed #6443: Redirect to project settings after Copying a Project
    -* Fixed #6448: Saving a wiki page with no content has a translation missing
    -* Fixed #6452: Unhandled exception on creating File
    -* Fixed #6471: Typo in label_report in Czech translation
    -* Fixed #6479: Changing tracker type will lose watchers
    -* Fixed #6499: Files with leading or trailing whitespace are not shown in git.
    -
    -== 2010-08-22 v1.0.1
    -
    -* #819: Add a body ID and class to all pages
    -* #871: Commit new CSS styles!
    -* #3301: Add favicon to base layout
    -* #4656: On Issue#show page, clicking on “Add related issue� should focus on the input
    -* #4896: Project identifier should be a limited field
    -* #5084: Filter all isssues by projects
    -* #5477: Replace Test::Unit::TestCase with ActiveSupport::TestCase
    -* #5591: 'calendar' action is used with 'issue' controller in issue/sidebar
    -* #5735: Traditional Chinese language file (to r3810)
    -* #5740: Swedish Translation for r3810
    -* #5785: pt-BR translation update
    -* #5898: Projects should be displayed as links in users/memberships
    -* #5910: Chinese translation to redmine-1.0.0
    -* #5912: Translation update for french locale
    -* #5962: Hungarian translation update to r3892
    -* #5971: Remove falsly applied chrome on revision links
    -* #5972: Updated Hebrew translation for 1.0.0
    -* #5982: Updated german translation
    -* #6008: Move admin_menu to Redmine::MenuManager
    -* #6012: RTL layout
    -* #6021: Spanish translation 1.0.0-RC
    -* #6025: nl translation updated for r3905
    -* #6030: Japanese Translation for r3907
    -* #6074: sr-CY.yml contains DOS-type newlines (\r\n)
    -* #6087: SERBIAN translation updated
    -* #6093: Updated italian translation
    -* #6142: Swedish Translation for r3940
    -* #6153: Move view_calendar and view_gantt to own modules
    -* #6169: Add issue status to issue tooltip
    -* Fixed #3834: Add a warning when not choosing a member role
    -* Fixed #3922: Bad english arround "Assigned to" text in journal entries
    -* Fixed #5158: Simplified Chinese language file zh.yml updated to r3608
    -* Fixed #5162: translation missing: zh-TW, field_time_entrie
    -* Fixed #5297: openid not validated correctly
    -* Fixed #5628: Wrong commit range in git log command
    -* Fixed #5760: Assigned_to and author filters in "Projects>View all issues" should be based on user's project visibility
    -* Fixed #5771: Problem when importing git repository
    -* Fixed #5775: ldap authentication in admin menu should have an icon
    -* Fixed #5811: deleting statuses doesnt delete workflow entries
    -* Fixed #5834: Emails with trailing spaces incorrectly detected as invalid
    -* Fixed #5846: ChangeChangesPathLengthLimit does not remove default for MySQL
    -* Fixed #5861: Vertical scrollbar always visible in Wiki "code" blocks in Chrome.
    -* Fixed #5883: correct label_project_latest Chinese translation
    -* Fixed #5892: Changing status from contextual menu opens the ticket instead
    -* Fixed #5904: Global gantt PDF and PNG should display project names
    -* Fixed #5925: parent task's priority edit should be disabled through shortcut menu in issues list page
    -* Fixed #5935: Add Another file to ticket doesn't work in IE Internet Explorer
    -* Fixed #5937: Harmonize french locale "zero" translation with other locales
    -* Fixed #5945: Forum message permalinks don't take pagination into account
    -* Fixed #5978: Debug code still remains
    -* Fixed #6009: When using "English (British)", the repository browser (svn) shows files over 1000 bytes as floating point (2.334355)
    -* Fixed #6045: Repository file Diff view sometimes shows more than selected file
    -* Fixed #6079: German Translation error in TimeEntryActivity
    -* Fixed #6100: User's profile should display all visible projects
    -* Fixed #6132: Allow Key based authentication in the Boards atom feed
    -* Fixed #6163: Bad CSS class for calendar project menu_item
    -* Fixed #6172: Browsing to a missing user's page shows the admin sidebar
    -
    -== 2010-07-18 v1.0.0 (Release candidate)
    -
    -* #443: Adds context menu to the roadmap issue lists
    -* #443: Subtasking
    -* #741: Description preview while editing an issue
    -* #1131: Add support for alternate (non-LDAP) authentication
    -* #1214: REST API for Issues
    -* #1223: File upload on wiki edit form
    -* #1755: add "blocked by" as a related issues option
    -* #2420: Fetching emails from an POP server
    -* #2482: Named scopes in Issue and ActsAsWatchable plus some view refactoring (logic extraction).
    -* #2924: Make the right click menu more discoverable using a cursor property
    -* #2985: Make syntax highlighting pluggable
    -* #3201: Workflow Check/Uncheck All Rows/Columns
    -* #3359: Update CodeRay 0.9
    -* #3706: Allow assigned_to field configuration on Issue creation by email
    -* #3936: configurable list of models to include in search
    -* #4480: Create a link to the user profile from the administration interface
    -* #4482: Cache textile rendering
    -* #4572: Make it harder to ruin your database
    -* #4573: Move github gems to Gemcutter
    -* #4664: Add pagination to forum threads
    -* #4732: Make login case-insensitive also for PostgreSQL
    -* #4812: Create links to other projects
    -* #4819: Replace images with smushed ones for speed
    -* #4945: Allow custom fields attached to project to be searchable
    -* #5121: Fix issues list layout overflow
    -* #5169: Issue list view hook request
    -* #5208: Aibility to edit wiki sidebar
    -* #5281: Remove empty ul tags in the issue history
    -* #5291: Updated basque translations
    -* #5328: Automatically add "Repository" menu_item after repository creation
    -* #5415: Fewer SQL statements generated for watcher_recipients
    -* #5416: Exclude "fields_for" from overridden methods in TabularFormBuilder
    -* #5573: Allow issue assignment in email
    -* #5595: Allow start date and due dates to be set via incoming email
    -* #5752: The projects view (/projects) renders ul's wrong
    -* #5781: Allow to use more macros on the welcome page and project list
    -* Fixed #1288: Unable to past escaped wiki syntax in an issue description
    -* Fixed #1334: Wiki formatting character *_ and _*
    -* Fixed #1416: Inline code with less-then/greater-than produces @lt; and @gt; respectively
    -* Fixed #2473: Login and mail should not be case sensitive
    -* Fixed #2990: Ruby 1.9 - wrong number of arguments (1 for 0) on rake db:migrate
    -* Fixed #3089: Text formatting sometimes breaks when combined
    -* Fixed #3690: Status change info duplicates on the issue screen
    -* Fixed #3691: Redmine allows two files with the same file name to be uploaded to the same issue
    -* Fixed #3764: ApplicationHelperTest fails with JRuby
    -* Fixed #4265: Unclosed code tags in issue descriptions affects main UI
    -* Fixed #4745: Bug in index.xml.builder (issues)
    -* Fixed #4852: changing user/roles of project member not possible without javascript
    -* Fixed #4857: Week number calculation in date picker is wrong if a week starts with Sunday
    -* Fixed #4883: Bottom "contextual" placement in issue with associated changeset
    -* Fixed #4918: Revisions r3453 and r3454 broke On-the-fly user creation with LDAP
    -* Fixed #4935: Navigation to the Master Timesheet page (time_entries)
    -* Fixed #5043: Flash messages are not displayed after the project settings[module/activity] saved
    -* Fixed #5081: Broken links on public/help/wiki_syntax_detailed.html
    -* Fixed #5104: Description of document not wikified on documents index
    -* Fixed #5108: Issue linking fails inside of []s
    -* Fixed #5199: diff code coloring using coderay
    -* Fixed #5233: Add a hook to the issue report (Summary) view
    -* Fixed #5265: timetracking: subtasks time is added to the main task
    -* Fixed #5343: acts_as_event Doesn't Accept Outside URLs
    -* Fixed #5440: UI Inconsistency : Administration > Enumerations table row headers should be enclosed in 
    -* Fixed #5463: 0.9.4 INSTALL and/or UPGRADE, missing session_store.rb
    -* Fixed #5524: Update_parent_attributes doesn't work for the old parent issue when reparenting
    -* Fixed #5548: SVN Repository: Can not list content of a folder which includes square brackets.
    -* Fixed #5589: "with subproject" malfunction
    -* Fixed #5676: Search for Numeric Value
    -* Fixed #5696: Redmine + PostgreSQL 8.4.4 fails on _dir_list_content.rhtml
    -* Fixed #5698: redmine:email:receive_imap fails silently for mails with subject longer than 255 characters
    -* Fixed #5700: TimelogController#destroy assumes success
    -* Fixed #5751: developer role is mispelled
    -* Fixed #5769: Popup Calendar doesn't Advance in Chrome
    -* Fixed #5771: Problem when importing git repository
    -* Fixed #5823: Error in comments in plugin.rb
    -
    -
    -== 2010-07-07 v0.9.6
    -
    -* Fixed: Redmine.pm access by unauthorized users
    -
    -== 2010-06-24 v0.9.5
    -
    -* Linkify folder names on revision view
    -* "fiters" and "options" should be hidden in print view via css
    -* Fixed: NoMethodError when no issue params are submitted
    -* Fixed: projects.atom with required authentication
    -* Fixed: External links not correctly displayed in Wiki TOC
    -* Fixed: Member role forms in project settings are not hidden after member added
    -* Fixed: pre can't be inside p
    -* Fixed: session cookie path does not respect RAILS_RELATIVE_URL_ROOT
    -* Fixed: mail handler fails when the from address is empty
    -
    -
    -== 2010-05-01 v0.9.4
    -
    -* Filters collapsed by default on issues index page for a saved query
    -* Fixed: When categories list is too big the popup menu doesn't adjust (ex. in the issue list)
    -* Fixed: remove "main-menu" div when the menu is empty
    -* Fixed: Code syntax highlighting not working in Document page
    -* Fixed: Git blame/annotate fails on moved files
    -* Fixed: Failing test in test_show_atom
    -* Fixed: Migrate from trac - not displayed Wikis
    -* Fixed: Email notifications on file upload sent to empty recipient list
    -* Fixed: Migrating from trac is not possible, fails to allocate memory
    -* Fixed: Lost password no longer flashes a confirmation message
    -* Fixed: Crash while deleting in-use enumeration
    -* Fixed: Hard coded English string at the selection of issue watchers
    -* Fixed: Bazaar v2.1.0 changed behaviour
    -* Fixed: Roadmap display can raise an exception if no trackers are selected
    -* Fixed: Gravatar breaks layout of "logged in" page
    -* Fixed: Reposman.rb on Windows
    -* Fixed: Possible error 500 while moving an issue to another project with SQLite
    -* Fixed: backslashes in issue description/note should be escaped when quoted
    -* Fixed: Long text in 
     disrupts Associated revisions
    -* Fixed: Links to missing wiki pages not red on project overview page
    -* Fixed: Cannot delete a project with subprojects that shares versions
    -* Fixed: Update of Subversion changesets broken under Solaris
    -* Fixed: "Move issues" permission not working for Non member
    -* Fixed: Sidebar overlap on Users tab of Group editor
    -* Fixed: Error on db:migrate with table prefix set (hardcoded name in principal.rb)
    -* Fixed: Report shows sub-projects for non-members
    -* Fixed: 500 internal error when browsing any Redmine page in epiphany
    -* Fixed: Watchers selection lost when issue creation fails
    -* Fixed: When copying projects, redmine should not generate an email to people who created issues
    -* Fixed: Issue "#" table cells should have a class attribute to enable fine-grained CSS theme
    -* Fixed: Plugin generators should display help if no parameter is given
    -
    -
    -== 2010-02-28 v0.9.3
    -
    -* Adds filter for system shared versions on the cross project issue list
    -* Makes project identifiers searchable
    -* Remove invalid utf8 sequences from commit comments and author name
    -* Fixed: Wrong link when "http" not included in project "Homepage" link
    -* Fixed: Escaping in html email templates
    -* Fixed: Pound (#) followed by number with leading zero (0) removes leading zero when rendered in wiki
    -* Fixed: Deselecting textile text formatting causes interning empty string errors
    -* Fixed: error with postgres when entering a non-numeric id for an issue relation
    -* Fixed: div.task incorrectly wrapping on Gantt Chart
    -* Fixed: Project copy loses wiki pages hierarchy
    -* Fixed: parent project field doesn't include blank value when a member with 'add subproject' permission edits a child project
    -* Fixed: Repository.fetch_changesets tries to fetch changesets for archived projects
    -* Fixed: Duplicated project name for subproject version on gantt chart
    -* Fixed: roadmap shows subprojects issues even if subprojects is unchecked
    -* Fixed: IndexError if all the :last menu items are deleted from a menu
    -* Fixed: Very high CPU usage for a long time when fetching commits from a large Git repository
    -
    -
    -== 2010-02-07 v0.9.2
    -
    -* Fixed: Sub-project repository commits not displayed on parent project issues
    -* Fixed: Potential security leak on my page calendar
    -* Fixed: Project tree structure is broken by deleting the project with the subproject
    -* Fixed: Error message shown duplicated when creating a new group
    -* Fixed: Firefox cuts off large pages
    -* Fixed: Invalid format parameter returns a DoubleRenderError on issues index
    -* Fixed: Unnecessary Quote button on locked forum message
    -* Fixed: Error raised when trying to view the gantt or calendar with a grouped query
    -* Fixed: PDF support for Korean locale
    -* Fixed: Deprecation warning in extra/svn/reposman.rb
    -
    -
    -== 2010-01-30 v0.9.1
    -
    -* Vertical alignment for inline images in formatted text set to 'middle'
    -* Fixed: Redmine.pm error "closing dbh with active statement handles at /usr/lib/perl5/Apache/Redmine.pm"
    -* Fixed: copyright year in footer set to 2010
    -* Fixed: Trac migration script may not output query lines
    -* Fixed: Email notifications may affect language of notice messages on the UI
    -* Fixed: Can not search for 2 letters word
    -* Fixed: Attachments get saved on issue update even if validation fails
    -* Fixed: Tab's 'border-bottom' not absent when selected
    -* Fixed: Issue summary tables that list by user are not sorted
    -* Fixed: Issue pdf export fails if target version is set
    -* Fixed: Issue list export to PDF breaks when issues are sorted by a custom field
    -* Fixed: SQL error when adding a group
    -* Fixes: Min password length during password reset always displays as 4 chars
    -
    -
    -== 2010-01-09 v0.9.0 (Release candidate)
    -
    -* Unlimited subproject nesting
    -* Multiple roles per user per project
    -* User groups
    -* Inheritence of versions
    -* OpenID login
    -* "Watched by me" issue filter
    -* Project copy
    -* Project creation by non admin users
    -* Accept emails from anyone on a private project
    -* Add email notification on Wiki changes
    -* Make issue description non-required field
    -* Custom fields for Versions
    -* Being able to sort the issue list by custom fields
    -* Ability to close versions
    -* User display/editing of custom fields attached to their user profile
    -* Add "follows" issue relation
    -* Copy workflows between trackers and roles
    -* Defaults enabled modules list for project creation
    -* Weighted version completion percentage on the roadmap
    -* Autocreate user account when user submits email that creates new issue
    -* CSS class on overdue issues on the issue list
    -* Enable tracker update on issue edit form
    -* Remove issue watchers
    -* Ability to move threads between project forums
    -* Changed custom field "Possible values" to a textarea
    -* Adds projects association on tracker form
    -* Set session store to cookie store by default
    -* Set a default wiki page on project creation
    -* Roadmap for main project should see Roadmaps for sub projects
    -* Ticket grouping on the issue list
    -* Hierarchical Project links in the page header
    -* Allow My Page blocks to be added to from a plugin
    -* Sort issues by multiple columns
    -* Filters of saved query are now visible and be adjusted without editing the query
    -* Saving "sort order" in custom queries
    -* Url to fetch changesets for a repository
    -* Managers able to create subprojects
    -* Issue Totals on My Page Modules
    -* Convert Enumerations to single table inheritance (STI)
    -* Allow custom my_page blocks to define drop-down names
    -* "View Issues" user permission added
    -* Ask user what to do with child pages when deleting a parent wiki page
    -* Contextual quick search
    -* Allow resending of password by email
    -* Change reply subject to be a link to the reply itself
    -* Include Logged Time as part of the project's Activity history
    -* REST API for authentication
    -* Browse through Git branches
    -* Setup Object Daddy to replace test fixtures
    -* Setup shoulda to make it easier to test
    -* Custom fields and overrides on Enumerations
    -* Add or remove columns from the issue list
    -* Ability to add new version from issues screen
    -* Setting to choose which day calendars start
    -* Asynchronous email delivery method
    -* RESTful URLs for (almost) everything
    -* Include issue status in search results and activity pages
    -* Add email to admin user search filter
    -* Proper content type for plain text mails
    -* Default value of project jump box
    -* Tree based menus
    -* Ability to use issue status to update percent done
    -* Second set of issue "Action Links" at the bottom of an issue page
    -* Proper exist status code for rdm-mailhandler.rb
    -* Remove incoming email body via a delimiter
    -* Fixed: Custom querry 'Export to PDF' ignores field selection
    -* Fixed: Related e-mail notifications aren't threaded
    -* Fixed: No warning when the creation of a categories from the issue form fails
    -* Fixed: Actually block issues from closing when relation 'blocked by' isn't closed
    -* Fixed: Include both first and last name when sorting by users
    -* Fixed: Table cell with multiple line text
    -* Fixed: Project overview page shows disabled trackers
    -* Fixed: Cross project issue relations and user permissions
    -* Fixed: My page shows tickets the user doesn't have access to
    -* Fixed: TOC does not parse wiki page reference links with description
    -* Fixed: Target version-list on bulk edit form is incorrectly sorted
    -* Fixed: Cannot modify/delete project named "Documents"
    -* Fixed: Email address in brackets breaks html
    -* Fixed: Timelog detail loose issue filter passing to report tab
    -* Fixed: Inform about custom field's name maximum length
    -* Fixed: Activity page and Atom feed links contain project id instead of identifier
    -* Fixed: no Atom key for forums with only 1 forum
    -* Fixed: When reading RSS feed in MS Outlook, the inline links are broken.
    -* Fixed: Sometimes new posts don't show up in the topic list of a forum.
    -* Fixed: The all/active filter selection in the project view does not stick.
    -* Fixed: Login box has Different width
    -* Fixed: User removed from project - still getting project update emails
    -* Fixed: Project with the identifier of 'new' cannot be viewed
    -* Fixed: Artefacts in search view (Cyrillic)
    -* Fixed: Allow [#id] as subject to reply by email
    -* Fixed: Wrong language used when closing an issue via a commit message
    -* Fixed: email handler drops emails for new issues with no subject
    -* Fixed: Calendar misspelled under Roles/Permissions
    -* Fixed: Emails from no-reply redmine's address hell cycle
    -* Fixed: child_pages macro fails on wiki page history
    -* Fixed: Pre-filled time tracking date ignores timezone
    -* Fixed: Links on locked users lead to 404 page
    -* Fixed: Page changes in issue-list when using context menu
    -* Fixed: diff parser removes lines starting with multiple dashes
    -* Fixed: Quoting in forums resets message subject
    -* Fixed: Editing issue comment removes quote link
    -* Fixed: Redmine.pm ignore browse_repository permission
    -* Fixed: text formatting breaks on [msg1][msg2]
    -* Fixed: Spent Time Default Value of 0.0
    -* Fixed: Wiki pages in search results are referenced by project number, not by project identifier.
    -* Fixed: When logging in via an autologin cookie the user's last_login_on should be updated
    -* Fixed: 50k users cause problems in project->settings->members screen
    -* Fixed: Document timestamp needs to show updated timestamps
    -* Fixed: Users getting notifications for issues they are no longer allowed to view
    -* Fixed: issue summary counts should link to the issue list without subprojects
    -* Fixed: 'Delete' link on LDAP list has no effect
    -
    -
    -== 2009-11-15 v0.8.7
    -
    -* Fixed: Hide paragraph terminator at the end of headings on html export
    -* Fixed: pre tags containing "