##// END OF EJS Templates
Adds named scopes for projects index....
Jean-Philippe Lang -
r7962:ff0f141126e4
parent child
Show More
@@ -1,84 +1,82
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AdminController < ApplicationController
18 class AdminController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20 before_filter :require_admin
20 before_filter :require_admin
21 helper :sort
21 helper :sort
22 include SortHelper
22 include SortHelper
23
23
24 def index
24 def index
25 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
25 @no_configuration_data = Redmine::DefaultData::Loader::no_data?
26 end
26 end
27
27
28 def projects
28 def projects
29 @status = params[:status] ? params[:status].to_i : 1
29 @status = params[:status] || 1
30 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
30
31 unless params[:name].blank?
31 scope = Project.status(@status)
32 name = "%#{params[:name].strip.downcase}%"
32 scope = scope.like(params[:name]) if params[:name].present?
33 c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
33
34 end
34 @projects = scope.all(:order => 'lft')
35 @projects = Project.find :all, :order => 'lft',
36 :conditions => c.conditions
37
35
38 render :action => "projects", :layout => false if request.xhr?
36 render :action => "projects", :layout => false if request.xhr?
39 end
37 end
40
38
41 def plugins
39 def plugins
42 @plugins = Redmine::Plugin.all
40 @plugins = Redmine::Plugin.all
43 end
41 end
44
42
45 # Loads the default configuration
43 # Loads the default configuration
46 # (roles, trackers, statuses, workflow, enumerations)
44 # (roles, trackers, statuses, workflow, enumerations)
47 def default_configuration
45 def default_configuration
48 if request.post?
46 if request.post?
49 begin
47 begin
50 Redmine::DefaultData::Loader::load(params[:lang])
48 Redmine::DefaultData::Loader::load(params[:lang])
51 flash[:notice] = l(:notice_default_data_loaded)
49 flash[:notice] = l(:notice_default_data_loaded)
52 rescue Exception => e
50 rescue Exception => e
53 flash[:error] = l(:error_can_t_load_default_data, e.message)
51 flash[:error] = l(:error_can_t_load_default_data, e.message)
54 end
52 end
55 end
53 end
56 redirect_to :action => 'index'
54 redirect_to :action => 'index'
57 end
55 end
58
56
59 def test_email
57 def test_email
60 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
58 raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
61 # Force ActionMailer to raise delivery errors so we can catch it
59 # Force ActionMailer to raise delivery errors so we can catch it
62 ActionMailer::Base.raise_delivery_errors = true
60 ActionMailer::Base.raise_delivery_errors = true
63 begin
61 begin
64 @test = Mailer.deliver_test(User.current)
62 @test = Mailer.deliver_test(User.current)
65 flash[:notice] = l(:notice_email_sent, User.current.mail)
63 flash[:notice] = l(:notice_email_sent, User.current.mail)
66 rescue Exception => e
64 rescue Exception => e
67 flash[:error] = l(:notice_email_error, e.message)
65 flash[:error] = l(:notice_email_error, e.message)
68 end
66 end
69 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
67 ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
70 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
68 redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications'
71 end
69 end
72
70
73 def info
71 def info
74 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
72 @db_adapter_name = ActiveRecord::Base.connection.adapter_name
75 @checklist = [
73 @checklist = [
76 [:text_default_administrator_account_changed,
74 [:text_default_administrator_account_changed,
77 User.find(:first,
75 User.find(:first,
78 :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
76 :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
79 [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
77 [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
80 [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
78 [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
81 [:text_rmagick_available, Object.const_defined?(:Magick)]
79 [:text_rmagick_available, Object.const_defined?(:Magick)]
82 ]
80 ]
83 end
81 end
84 end
82 end
@@ -1,23 +1,23
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module AdminHelper
18 module AdminHelper
19 def project_status_options_for_select(selected)
19 def project_status_options_for_select(selected)
20 options_for_select([[l(:label_all), ''],
20 options_for_select([[l(:label_all), ''],
21 [l(:status_active), 1]], selected)
21 [l(:status_active), '1']], selected.to_s)
22 end
22 end
23 end
23 end
@@ -1,879 +1,888
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 # Project statuses
21 # Project statuses
22 STATUS_ACTIVE = 1
22 STATUS_ACTIVE = 1
23 STATUS_ARCHIVED = 9
23 STATUS_ARCHIVED = 9
24
24
25 # Maximum length for project identifiers
25 # Maximum length for project identifiers
26 IDENTIFIER_MAX_LENGTH = 100
26 IDENTIFIER_MAX_LENGTH = 100
27
27
28 # Specific overidden Activities
28 # Specific overidden Activities
29 has_many :time_entry_activities
29 has_many :time_entry_activities
30 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
30 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
31 has_many :memberships, :class_name => 'Member'
31 has_many :memberships, :class_name => 'Member'
32 has_many :member_principals, :class_name => 'Member',
32 has_many :member_principals, :class_name => 'Member',
33 :include => :principal,
33 :include => :principal,
34 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
34 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
35 has_many :users, :through => :members
35 has_many :users, :through => :members
36 has_many :principals, :through => :member_principals, :source => :principal
36 has_many :principals, :through => :member_principals, :source => :principal
37
37
38 has_many :enabled_modules, :dependent => :delete_all
38 has_many :enabled_modules, :dependent => :delete_all
39 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
39 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
40 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
40 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
41 has_many :issue_changes, :through => :issues, :source => :journals
41 has_many :issue_changes, :through => :issues, :source => :journals
42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
43 has_many :time_entries, :dependent => :delete_all
43 has_many :time_entries, :dependent => :delete_all
44 has_many :queries, :dependent => :delete_all
44 has_many :queries, :dependent => :delete_all
45 has_many :documents, :dependent => :destroy
45 has_many :documents, :dependent => :destroy
46 has_many :news, :dependent => :destroy, :include => :author
46 has_many :news, :dependent => :destroy, :include => :author
47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
48 has_many :boards, :dependent => :destroy, :order => "position ASC"
48 has_many :boards, :dependent => :destroy, :order => "position ASC"
49 has_one :repository, :dependent => :destroy
49 has_one :repository, :dependent => :destroy
50 has_many :changesets, :through => :repository
50 has_many :changesets, :through => :repository
51 has_one :wiki, :dependent => :destroy
51 has_one :wiki, :dependent => :destroy
52 # Custom field for the project issues
52 # Custom field for the project issues
53 has_and_belongs_to_many :issue_custom_fields,
53 has_and_belongs_to_many :issue_custom_fields,
54 :class_name => 'IssueCustomField',
54 :class_name => 'IssueCustomField',
55 :order => "#{CustomField.table_name}.position",
55 :order => "#{CustomField.table_name}.position",
56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
57 :association_foreign_key => 'custom_field_id'
57 :association_foreign_key => 'custom_field_id'
58
58
59 acts_as_nested_set :order => 'name', :dependent => :destroy
59 acts_as_nested_set :order => 'name', :dependent => :destroy
60 acts_as_attachable :view_permission => :view_files,
60 acts_as_attachable :view_permission => :view_files,
61 :delete_permission => :manage_files
61 :delete_permission => :manage_files
62
62
63 acts_as_customizable
63 acts_as_customizable
64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
65 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
65 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
67 :author => nil
67 :author => nil
68
68
69 attr_protected :status
69 attr_protected :status
70
70
71 validates_presence_of :name, :identifier
71 validates_presence_of :name, :identifier
72 validates_uniqueness_of :identifier
72 validates_uniqueness_of :identifier
73 validates_associated :repository, :wiki
73 validates_associated :repository, :wiki
74 validates_length_of :name, :maximum => 255
74 validates_length_of :name, :maximum => 255
75 validates_length_of :homepage, :maximum => 255
75 validates_length_of :homepage, :maximum => 255
76 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
76 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
77 # donwcase letters, digits, dashes but not digits only
77 # donwcase letters, digits, dashes but not digits only
78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
79 # reserved words
79 # reserved words
80 validates_exclusion_of :identifier, :in => %w( new )
80 validates_exclusion_of :identifier, :in => %w( new )
81
81
82 before_destroy :delete_all_members
82 before_destroy :delete_all_members
83
83
84 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
84 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
85 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
85 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
86 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
86 named_scope :all_public, { :conditions => { :is_public => true } }
87 named_scope :all_public, { :conditions => { :is_public => true } }
87 named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }}
88 named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }}
89 named_scope :like, lambda {|arg|
90 if arg.blank?
91 {}
92 else
93 pattern = "%#{arg.to_s.strip.downcase}%"
94 {:conditions => ["LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", {:p => pattern}]}
95 end
96 }
88
97
89 def initialize(attributes = nil)
98 def initialize(attributes = nil)
90 super
99 super
91
100
92 initialized = (attributes || {}).stringify_keys
101 initialized = (attributes || {}).stringify_keys
93 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
102 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
94 self.identifier = Project.next_identifier
103 self.identifier = Project.next_identifier
95 end
104 end
96 if !initialized.key?('is_public')
105 if !initialized.key?('is_public')
97 self.is_public = Setting.default_projects_public?
106 self.is_public = Setting.default_projects_public?
98 end
107 end
99 if !initialized.key?('enabled_module_names')
108 if !initialized.key?('enabled_module_names')
100 self.enabled_module_names = Setting.default_projects_modules
109 self.enabled_module_names = Setting.default_projects_modules
101 end
110 end
102 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
111 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
103 self.trackers = Tracker.all
112 self.trackers = Tracker.all
104 end
113 end
105 end
114 end
106
115
107 def identifier=(identifier)
116 def identifier=(identifier)
108 super unless identifier_frozen?
117 super unless identifier_frozen?
109 end
118 end
110
119
111 def identifier_frozen?
120 def identifier_frozen?
112 errors[:identifier].nil? && !(new_record? || identifier.blank?)
121 errors[:identifier].nil? && !(new_record? || identifier.blank?)
113 end
122 end
114
123
115 # returns latest created projects
124 # returns latest created projects
116 # non public projects will be returned only if user is a member of those
125 # non public projects will be returned only if user is a member of those
117 def self.latest(user=nil, count=5)
126 def self.latest(user=nil, count=5)
118 visible(user).find(:all, :limit => count, :order => "created_on DESC")
127 visible(user).find(:all, :limit => count, :order => "created_on DESC")
119 end
128 end
120
129
121 # Returns true if the project is visible to +user+ or to the current user.
130 # Returns true if the project is visible to +user+ or to the current user.
122 def visible?(user=User.current)
131 def visible?(user=User.current)
123 user.allowed_to?(:view_project, self)
132 user.allowed_to?(:view_project, self)
124 end
133 end
125
134
126 # Returns a SQL conditions string used to find all projects visible by the specified user.
135 # Returns a SQL conditions string used to find all projects visible by the specified user.
127 #
136 #
128 # Examples:
137 # Examples:
129 # Project.visible_condition(admin) => "projects.status = 1"
138 # Project.visible_condition(admin) => "projects.status = 1"
130 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
139 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
131 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
140 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
132 def self.visible_condition(user, options={})
141 def self.visible_condition(user, options={})
133 allowed_to_condition(user, :view_project, options)
142 allowed_to_condition(user, :view_project, options)
134 end
143 end
135
144
136 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
145 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
137 #
146 #
138 # Valid options:
147 # Valid options:
139 # * :project => limit the condition to project
148 # * :project => limit the condition to project
140 # * :with_subprojects => limit the condition to project and its subprojects
149 # * :with_subprojects => limit the condition to project and its subprojects
141 # * :member => limit the condition to the user projects
150 # * :member => limit the condition to the user projects
142 def self.allowed_to_condition(user, permission, options={})
151 def self.allowed_to_condition(user, permission, options={})
143 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
152 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
144 if perm = Redmine::AccessControl.permission(permission)
153 if perm = Redmine::AccessControl.permission(permission)
145 unless perm.project_module.nil?
154 unless perm.project_module.nil?
146 # If the permission belongs to a project module, make sure the module is enabled
155 # If the permission belongs to a project module, make sure the module is enabled
147 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
156 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
148 end
157 end
149 end
158 end
150 if options[:project]
159 if options[:project]
151 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
160 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
152 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
161 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
153 base_statement = "(#{project_statement}) AND (#{base_statement})"
162 base_statement = "(#{project_statement}) AND (#{base_statement})"
154 end
163 end
155
164
156 if user.admin?
165 if user.admin?
157 base_statement
166 base_statement
158 else
167 else
159 statement_by_role = {}
168 statement_by_role = {}
160 unless options[:member]
169 unless options[:member]
161 role = user.logged? ? Role.non_member : Role.anonymous
170 role = user.logged? ? Role.non_member : Role.anonymous
162 if role.allowed_to?(permission)
171 if role.allowed_to?(permission)
163 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
172 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
164 end
173 end
165 end
174 end
166 if user.logged?
175 if user.logged?
167 user.projects_by_role.each do |role, projects|
176 user.projects_by_role.each do |role, projects|
168 if role.allowed_to?(permission)
177 if role.allowed_to?(permission)
169 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
178 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
170 end
179 end
171 end
180 end
172 end
181 end
173 if statement_by_role.empty?
182 if statement_by_role.empty?
174 "1=0"
183 "1=0"
175 else
184 else
176 if block_given?
185 if block_given?
177 statement_by_role.each do |role, statement|
186 statement_by_role.each do |role, statement|
178 if s = yield(role, user)
187 if s = yield(role, user)
179 statement_by_role[role] = "(#{statement} AND (#{s}))"
188 statement_by_role[role] = "(#{statement} AND (#{s}))"
180 end
189 end
181 end
190 end
182 end
191 end
183 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
192 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
184 end
193 end
185 end
194 end
186 end
195 end
187
196
188 # Returns the Systemwide and project specific activities
197 # Returns the Systemwide and project specific activities
189 def activities(include_inactive=false)
198 def activities(include_inactive=false)
190 if include_inactive
199 if include_inactive
191 return all_activities
200 return all_activities
192 else
201 else
193 return active_activities
202 return active_activities
194 end
203 end
195 end
204 end
196
205
197 # Will create a new Project specific Activity or update an existing one
206 # Will create a new Project specific Activity or update an existing one
198 #
207 #
199 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
208 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
200 # does not successfully save.
209 # does not successfully save.
201 def update_or_create_time_entry_activity(id, activity_hash)
210 def update_or_create_time_entry_activity(id, activity_hash)
202 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
211 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
203 self.create_time_entry_activity_if_needed(activity_hash)
212 self.create_time_entry_activity_if_needed(activity_hash)
204 else
213 else
205 activity = project.time_entry_activities.find_by_id(id.to_i)
214 activity = project.time_entry_activities.find_by_id(id.to_i)
206 activity.update_attributes(activity_hash) if activity
215 activity.update_attributes(activity_hash) if activity
207 end
216 end
208 end
217 end
209
218
210 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
219 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
211 #
220 #
212 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
221 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
213 # does not successfully save.
222 # does not successfully save.
214 def create_time_entry_activity_if_needed(activity)
223 def create_time_entry_activity_if_needed(activity)
215 if activity['parent_id']
224 if activity['parent_id']
216
225
217 parent_activity = TimeEntryActivity.find(activity['parent_id'])
226 parent_activity = TimeEntryActivity.find(activity['parent_id'])
218 activity['name'] = parent_activity.name
227 activity['name'] = parent_activity.name
219 activity['position'] = parent_activity.position
228 activity['position'] = parent_activity.position
220
229
221 if Enumeration.overridding_change?(activity, parent_activity)
230 if Enumeration.overridding_change?(activity, parent_activity)
222 project_activity = self.time_entry_activities.create(activity)
231 project_activity = self.time_entry_activities.create(activity)
223
232
224 if project_activity.new_record?
233 if project_activity.new_record?
225 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
234 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
226 else
235 else
227 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
236 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
228 end
237 end
229 end
238 end
230 end
239 end
231 end
240 end
232
241
233 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
242 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
234 #
243 #
235 # Examples:
244 # Examples:
236 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
245 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
237 # project.project_condition(false) => "projects.id = 1"
246 # project.project_condition(false) => "projects.id = 1"
238 def project_condition(with_subprojects)
247 def project_condition(with_subprojects)
239 cond = "#{Project.table_name}.id = #{id}"
248 cond = "#{Project.table_name}.id = #{id}"
240 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
249 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
241 cond
250 cond
242 end
251 end
243
252
244 def self.find(*args)
253 def self.find(*args)
245 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
254 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
246 project = find_by_identifier(*args)
255 project = find_by_identifier(*args)
247 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
256 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
248 project
257 project
249 else
258 else
250 super
259 super
251 end
260 end
252 end
261 end
253
262
254 def to_param
263 def to_param
255 # id is used for projects with a numeric identifier (compatibility)
264 # id is used for projects with a numeric identifier (compatibility)
256 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
265 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
257 end
266 end
258
267
259 def active?
268 def active?
260 self.status == STATUS_ACTIVE
269 self.status == STATUS_ACTIVE
261 end
270 end
262
271
263 def archived?
272 def archived?
264 self.status == STATUS_ARCHIVED
273 self.status == STATUS_ARCHIVED
265 end
274 end
266
275
267 # Archives the project and its descendants
276 # Archives the project and its descendants
268 def archive
277 def archive
269 # Check that there is no issue of a non descendant project that is assigned
278 # Check that there is no issue of a non descendant project that is assigned
270 # to one of the project or descendant versions
279 # to one of the project or descendant versions
271 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
280 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
272 if v_ids.any? && Issue.find(:first, :include => :project,
281 if v_ids.any? && Issue.find(:first, :include => :project,
273 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
282 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
274 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
283 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
275 return false
284 return false
276 end
285 end
277 Project.transaction do
286 Project.transaction do
278 archive!
287 archive!
279 end
288 end
280 true
289 true
281 end
290 end
282
291
283 # Unarchives the project
292 # Unarchives the project
284 # All its ancestors must be active
293 # All its ancestors must be active
285 def unarchive
294 def unarchive
286 return false if ancestors.detect {|a| !a.active?}
295 return false if ancestors.detect {|a| !a.active?}
287 update_attribute :status, STATUS_ACTIVE
296 update_attribute :status, STATUS_ACTIVE
288 end
297 end
289
298
290 # Returns an array of projects the project can be moved to
299 # Returns an array of projects the project can be moved to
291 # by the current user
300 # by the current user
292 def allowed_parents
301 def allowed_parents
293 return @allowed_parents if @allowed_parents
302 return @allowed_parents if @allowed_parents
294 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
303 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
295 @allowed_parents = @allowed_parents - self_and_descendants
304 @allowed_parents = @allowed_parents - self_and_descendants
296 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
305 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
297 @allowed_parents << nil
306 @allowed_parents << nil
298 end
307 end
299 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
308 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
300 @allowed_parents << parent
309 @allowed_parents << parent
301 end
310 end
302 @allowed_parents
311 @allowed_parents
303 end
312 end
304
313
305 # Sets the parent of the project with authorization check
314 # Sets the parent of the project with authorization check
306 def set_allowed_parent!(p)
315 def set_allowed_parent!(p)
307 unless p.nil? || p.is_a?(Project)
316 unless p.nil? || p.is_a?(Project)
308 if p.to_s.blank?
317 if p.to_s.blank?
309 p = nil
318 p = nil
310 else
319 else
311 p = Project.find_by_id(p)
320 p = Project.find_by_id(p)
312 return false unless p
321 return false unless p
313 end
322 end
314 end
323 end
315 if p.nil?
324 if p.nil?
316 if !new_record? && allowed_parents.empty?
325 if !new_record? && allowed_parents.empty?
317 return false
326 return false
318 end
327 end
319 elsif !allowed_parents.include?(p)
328 elsif !allowed_parents.include?(p)
320 return false
329 return false
321 end
330 end
322 set_parent!(p)
331 set_parent!(p)
323 end
332 end
324
333
325 # Sets the parent of the project
334 # Sets the parent of the project
326 # Argument can be either a Project, a String, a Fixnum or nil
335 # Argument can be either a Project, a String, a Fixnum or nil
327 def set_parent!(p)
336 def set_parent!(p)
328 unless p.nil? || p.is_a?(Project)
337 unless p.nil? || p.is_a?(Project)
329 if p.to_s.blank?
338 if p.to_s.blank?
330 p = nil
339 p = nil
331 else
340 else
332 p = Project.find_by_id(p)
341 p = Project.find_by_id(p)
333 return false unless p
342 return false unless p
334 end
343 end
335 end
344 end
336 if p == parent && !p.nil?
345 if p == parent && !p.nil?
337 # Nothing to do
346 # Nothing to do
338 true
347 true
339 elsif p.nil? || (p.active? && move_possible?(p))
348 elsif p.nil? || (p.active? && move_possible?(p))
340 # Insert the project so that target's children or root projects stay alphabetically sorted
349 # Insert the project so that target's children or root projects stay alphabetically sorted
341 sibs = (p.nil? ? self.class.roots : p.children)
350 sibs = (p.nil? ? self.class.roots : p.children)
342 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
351 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
343 if to_be_inserted_before
352 if to_be_inserted_before
344 move_to_left_of(to_be_inserted_before)
353 move_to_left_of(to_be_inserted_before)
345 elsif p.nil?
354 elsif p.nil?
346 if sibs.empty?
355 if sibs.empty?
347 # move_to_root adds the project in first (ie. left) position
356 # move_to_root adds the project in first (ie. left) position
348 move_to_root
357 move_to_root
349 else
358 else
350 move_to_right_of(sibs.last) unless self == sibs.last
359 move_to_right_of(sibs.last) unless self == sibs.last
351 end
360 end
352 else
361 else
353 # move_to_child_of adds the project in last (ie.right) position
362 # move_to_child_of adds the project in last (ie.right) position
354 move_to_child_of(p)
363 move_to_child_of(p)
355 end
364 end
356 Issue.update_versions_from_hierarchy_change(self)
365 Issue.update_versions_from_hierarchy_change(self)
357 true
366 true
358 else
367 else
359 # Can not move to the given target
368 # Can not move to the given target
360 false
369 false
361 end
370 end
362 end
371 end
363
372
364 # Returns an array of the trackers used by the project and its active sub projects
373 # Returns an array of the trackers used by the project and its active sub projects
365 def rolled_up_trackers
374 def rolled_up_trackers
366 @rolled_up_trackers ||=
375 @rolled_up_trackers ||=
367 Tracker.find(:all, :joins => :projects,
376 Tracker.find(:all, :joins => :projects,
368 :select => "DISTINCT #{Tracker.table_name}.*",
377 :select => "DISTINCT #{Tracker.table_name}.*",
369 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
378 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
370 :order => "#{Tracker.table_name}.position")
379 :order => "#{Tracker.table_name}.position")
371 end
380 end
372
381
373 # Closes open and locked project versions that are completed
382 # Closes open and locked project versions that are completed
374 def close_completed_versions
383 def close_completed_versions
375 Version.transaction do
384 Version.transaction do
376 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
385 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
377 if version.completed?
386 if version.completed?
378 version.update_attribute(:status, 'closed')
387 version.update_attribute(:status, 'closed')
379 end
388 end
380 end
389 end
381 end
390 end
382 end
391 end
383
392
384 # Returns a scope of the Versions on subprojects
393 # Returns a scope of the Versions on subprojects
385 def rolled_up_versions
394 def rolled_up_versions
386 @rolled_up_versions ||=
395 @rolled_up_versions ||=
387 Version.scoped(:include => :project,
396 Version.scoped(:include => :project,
388 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
397 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
389 end
398 end
390
399
391 # Returns a scope of the Versions used by the project
400 # Returns a scope of the Versions used by the project
392 def shared_versions
401 def shared_versions
393 @shared_versions ||= begin
402 @shared_versions ||= begin
394 r = root? ? self : root
403 r = root? ? self : root
395 Version.scoped(:include => :project,
404 Version.scoped(:include => :project,
396 :conditions => "#{Project.table_name}.id = #{id}" +
405 :conditions => "#{Project.table_name}.id = #{id}" +
397 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
406 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
398 " #{Version.table_name}.sharing = 'system'" +
407 " #{Version.table_name}.sharing = 'system'" +
399 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
408 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
400 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
409 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
401 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
410 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
402 "))")
411 "))")
403 end
412 end
404 end
413 end
405
414
406 # Returns a hash of project users grouped by role
415 # Returns a hash of project users grouped by role
407 def users_by_role
416 def users_by_role
408 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
417 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
409 m.roles.each do |r|
418 m.roles.each do |r|
410 h[r] ||= []
419 h[r] ||= []
411 h[r] << m.user
420 h[r] << m.user
412 end
421 end
413 h
422 h
414 end
423 end
415 end
424 end
416
425
417 # Deletes all project's members
426 # Deletes all project's members
418 def delete_all_members
427 def delete_all_members
419 me, mr = Member.table_name, MemberRole.table_name
428 me, mr = Member.table_name, MemberRole.table_name
420 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
429 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
421 Member.delete_all(['project_id = ?', id])
430 Member.delete_all(['project_id = ?', id])
422 end
431 end
423
432
424 # Users/groups issues can be assigned to
433 # Users/groups issues can be assigned to
425 def assignable_users
434 def assignable_users
426 assignable = Setting.issue_group_assignment? ? member_principals : members
435 assignable = Setting.issue_group_assignment? ? member_principals : members
427 assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
436 assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
428 end
437 end
429
438
430 # Returns the mail adresses of users that should be always notified on project events
439 # Returns the mail adresses of users that should be always notified on project events
431 def recipients
440 def recipients
432 notified_users.collect {|user| user.mail}
441 notified_users.collect {|user| user.mail}
433 end
442 end
434
443
435 # Returns the users that should be notified on project events
444 # Returns the users that should be notified on project events
436 def notified_users
445 def notified_users
437 # TODO: User part should be extracted to User#notify_about?
446 # TODO: User part should be extracted to User#notify_about?
438 members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
447 members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
439 end
448 end
440
449
441 # Returns an array of all custom fields enabled for project issues
450 # Returns an array of all custom fields enabled for project issues
442 # (explictly associated custom fields and custom fields enabled for all projects)
451 # (explictly associated custom fields and custom fields enabled for all projects)
443 def all_issue_custom_fields
452 def all_issue_custom_fields
444 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
453 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
445 end
454 end
446
455
447 # Returns an array of all custom fields enabled for project time entries
456 # Returns an array of all custom fields enabled for project time entries
448 # (explictly associated custom fields and custom fields enabled for all projects)
457 # (explictly associated custom fields and custom fields enabled for all projects)
449 def all_time_entry_custom_fields
458 def all_time_entry_custom_fields
450 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
459 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
451 end
460 end
452
461
453 def project
462 def project
454 self
463 self
455 end
464 end
456
465
457 def <=>(project)
466 def <=>(project)
458 name.downcase <=> project.name.downcase
467 name.downcase <=> project.name.downcase
459 end
468 end
460
469
461 def to_s
470 def to_s
462 name
471 name
463 end
472 end
464
473
465 # Returns a short description of the projects (first lines)
474 # Returns a short description of the projects (first lines)
466 def short_description(length = 255)
475 def short_description(length = 255)
467 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
476 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
468 end
477 end
469
478
470 def css_classes
479 def css_classes
471 s = 'project'
480 s = 'project'
472 s << ' root' if root?
481 s << ' root' if root?
473 s << ' child' if child?
482 s << ' child' if child?
474 s << (leaf? ? ' leaf' : ' parent')
483 s << (leaf? ? ' leaf' : ' parent')
475 s
484 s
476 end
485 end
477
486
478 # The earliest start date of a project, based on it's issues and versions
487 # The earliest start date of a project, based on it's issues and versions
479 def start_date
488 def start_date
480 [
489 [
481 issues.minimum('start_date'),
490 issues.minimum('start_date'),
482 shared_versions.collect(&:effective_date),
491 shared_versions.collect(&:effective_date),
483 shared_versions.collect(&:start_date)
492 shared_versions.collect(&:start_date)
484 ].flatten.compact.min
493 ].flatten.compact.min
485 end
494 end
486
495
487 # The latest due date of an issue or version
496 # The latest due date of an issue or version
488 def due_date
497 def due_date
489 [
498 [
490 issues.maximum('due_date'),
499 issues.maximum('due_date'),
491 shared_versions.collect(&:effective_date),
500 shared_versions.collect(&:effective_date),
492 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
501 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
493 ].flatten.compact.max
502 ].flatten.compact.max
494 end
503 end
495
504
496 def overdue?
505 def overdue?
497 active? && !due_date.nil? && (due_date < Date.today)
506 active? && !due_date.nil? && (due_date < Date.today)
498 end
507 end
499
508
500 # Returns the percent completed for this project, based on the
509 # Returns the percent completed for this project, based on the
501 # progress on it's versions.
510 # progress on it's versions.
502 def completed_percent(options={:include_subprojects => false})
511 def completed_percent(options={:include_subprojects => false})
503 if options.delete(:include_subprojects)
512 if options.delete(:include_subprojects)
504 total = self_and_descendants.collect(&:completed_percent).sum
513 total = self_and_descendants.collect(&:completed_percent).sum
505
514
506 total / self_and_descendants.count
515 total / self_and_descendants.count
507 else
516 else
508 if versions.count > 0
517 if versions.count > 0
509 total = versions.collect(&:completed_pourcent).sum
518 total = versions.collect(&:completed_pourcent).sum
510
519
511 total / versions.count
520 total / versions.count
512 else
521 else
513 100
522 100
514 end
523 end
515 end
524 end
516 end
525 end
517
526
518 # Return true if this project is allowed to do the specified action.
527 # Return true if this project is allowed to do the specified action.
519 # action can be:
528 # action can be:
520 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
529 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
521 # * a permission Symbol (eg. :edit_project)
530 # * a permission Symbol (eg. :edit_project)
522 def allows_to?(action)
531 def allows_to?(action)
523 if action.is_a? Hash
532 if action.is_a? Hash
524 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
533 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
525 else
534 else
526 allowed_permissions.include? action
535 allowed_permissions.include? action
527 end
536 end
528 end
537 end
529
538
530 def module_enabled?(module_name)
539 def module_enabled?(module_name)
531 module_name = module_name.to_s
540 module_name = module_name.to_s
532 enabled_modules.detect {|m| m.name == module_name}
541 enabled_modules.detect {|m| m.name == module_name}
533 end
542 end
534
543
535 def enabled_module_names=(module_names)
544 def enabled_module_names=(module_names)
536 if module_names && module_names.is_a?(Array)
545 if module_names && module_names.is_a?(Array)
537 module_names = module_names.collect(&:to_s).reject(&:blank?)
546 module_names = module_names.collect(&:to_s).reject(&:blank?)
538 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
547 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
539 else
548 else
540 enabled_modules.clear
549 enabled_modules.clear
541 end
550 end
542 end
551 end
543
552
544 # Returns an array of the enabled modules names
553 # Returns an array of the enabled modules names
545 def enabled_module_names
554 def enabled_module_names
546 enabled_modules.collect(&:name)
555 enabled_modules.collect(&:name)
547 end
556 end
548
557
549 # Enable a specific module
558 # Enable a specific module
550 #
559 #
551 # Examples:
560 # Examples:
552 # project.enable_module!(:issue_tracking)
561 # project.enable_module!(:issue_tracking)
553 # project.enable_module!("issue_tracking")
562 # project.enable_module!("issue_tracking")
554 def enable_module!(name)
563 def enable_module!(name)
555 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
564 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
556 end
565 end
557
566
558 # Disable a module if it exists
567 # Disable a module if it exists
559 #
568 #
560 # Examples:
569 # Examples:
561 # project.disable_module!(:issue_tracking)
570 # project.disable_module!(:issue_tracking)
562 # project.disable_module!("issue_tracking")
571 # project.disable_module!("issue_tracking")
563 # project.disable_module!(project.enabled_modules.first)
572 # project.disable_module!(project.enabled_modules.first)
564 def disable_module!(target)
573 def disable_module!(target)
565 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
574 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
566 target.destroy unless target.blank?
575 target.destroy unless target.blank?
567 end
576 end
568
577
569 safe_attributes 'name',
578 safe_attributes 'name',
570 'description',
579 'description',
571 'homepage',
580 'homepage',
572 'is_public',
581 'is_public',
573 'identifier',
582 'identifier',
574 'custom_field_values',
583 'custom_field_values',
575 'custom_fields',
584 'custom_fields',
576 'tracker_ids',
585 'tracker_ids',
577 'issue_custom_field_ids'
586 'issue_custom_field_ids'
578
587
579 safe_attributes 'enabled_module_names',
588 safe_attributes 'enabled_module_names',
580 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
589 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
581
590
582 # Returns an array of projects that are in this project's hierarchy
591 # Returns an array of projects that are in this project's hierarchy
583 #
592 #
584 # Example: parents, children, siblings
593 # Example: parents, children, siblings
585 def hierarchy
594 def hierarchy
586 parents = project.self_and_ancestors || []
595 parents = project.self_and_ancestors || []
587 descendants = project.descendants || []
596 descendants = project.descendants || []
588 project_hierarchy = parents | descendants # Set union
597 project_hierarchy = parents | descendants # Set union
589 end
598 end
590
599
591 # Returns an auto-generated project identifier based on the last identifier used
600 # Returns an auto-generated project identifier based on the last identifier used
592 def self.next_identifier
601 def self.next_identifier
593 p = Project.find(:first, :order => 'created_on DESC')
602 p = Project.find(:first, :order => 'created_on DESC')
594 p.nil? ? nil : p.identifier.to_s.succ
603 p.nil? ? nil : p.identifier.to_s.succ
595 end
604 end
596
605
597 # Copies and saves the Project instance based on the +project+.
606 # Copies and saves the Project instance based on the +project+.
598 # Duplicates the source project's:
607 # Duplicates the source project's:
599 # * Wiki
608 # * Wiki
600 # * Versions
609 # * Versions
601 # * Categories
610 # * Categories
602 # * Issues
611 # * Issues
603 # * Members
612 # * Members
604 # * Queries
613 # * Queries
605 #
614 #
606 # Accepts an +options+ argument to specify what to copy
615 # Accepts an +options+ argument to specify what to copy
607 #
616 #
608 # Examples:
617 # Examples:
609 # project.copy(1) # => copies everything
618 # project.copy(1) # => copies everything
610 # project.copy(1, :only => 'members') # => copies members only
619 # project.copy(1, :only => 'members') # => copies members only
611 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
620 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
612 def copy(project, options={})
621 def copy(project, options={})
613 project = project.is_a?(Project) ? project : Project.find(project)
622 project = project.is_a?(Project) ? project : Project.find(project)
614
623
615 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
624 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
616 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
625 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
617
626
618 Project.transaction do
627 Project.transaction do
619 if save
628 if save
620 reload
629 reload
621 to_be_copied.each do |name|
630 to_be_copied.each do |name|
622 send "copy_#{name}", project
631 send "copy_#{name}", project
623 end
632 end
624 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
633 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
625 save
634 save
626 end
635 end
627 end
636 end
628 end
637 end
629
638
630
639
631 # Copies +project+ and returns the new instance. This will not save
640 # Copies +project+ and returns the new instance. This will not save
632 # the copy
641 # the copy
633 def self.copy_from(project)
642 def self.copy_from(project)
634 begin
643 begin
635 project = project.is_a?(Project) ? project : Project.find(project)
644 project = project.is_a?(Project) ? project : Project.find(project)
636 if project
645 if project
637 # clear unique attributes
646 # clear unique attributes
638 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
647 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
639 copy = Project.new(attributes)
648 copy = Project.new(attributes)
640 copy.enabled_modules = project.enabled_modules
649 copy.enabled_modules = project.enabled_modules
641 copy.trackers = project.trackers
650 copy.trackers = project.trackers
642 copy.custom_values = project.custom_values.collect {|v| v.clone}
651 copy.custom_values = project.custom_values.collect {|v| v.clone}
643 copy.issue_custom_fields = project.issue_custom_fields
652 copy.issue_custom_fields = project.issue_custom_fields
644 return copy
653 return copy
645 else
654 else
646 return nil
655 return nil
647 end
656 end
648 rescue ActiveRecord::RecordNotFound
657 rescue ActiveRecord::RecordNotFound
649 return nil
658 return nil
650 end
659 end
651 end
660 end
652
661
653 # Yields the given block for each project with its level in the tree
662 # Yields the given block for each project with its level in the tree
654 def self.project_tree(projects, &block)
663 def self.project_tree(projects, &block)
655 ancestors = []
664 ancestors = []
656 projects.sort_by(&:lft).each do |project|
665 projects.sort_by(&:lft).each do |project|
657 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
666 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
658 ancestors.pop
667 ancestors.pop
659 end
668 end
660 yield project, ancestors.size
669 yield project, ancestors.size
661 ancestors << project
670 ancestors << project
662 end
671 end
663 end
672 end
664
673
665 private
674 private
666
675
667 # Copies wiki from +project+
676 # Copies wiki from +project+
668 def copy_wiki(project)
677 def copy_wiki(project)
669 # Check that the source project has a wiki first
678 # Check that the source project has a wiki first
670 unless project.wiki.nil?
679 unless project.wiki.nil?
671 self.wiki ||= Wiki.new
680 self.wiki ||= Wiki.new
672 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
681 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
673 wiki_pages_map = {}
682 wiki_pages_map = {}
674 project.wiki.pages.each do |page|
683 project.wiki.pages.each do |page|
675 # Skip pages without content
684 # Skip pages without content
676 next if page.content.nil?
685 next if page.content.nil?
677 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
686 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
678 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
687 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
679 new_wiki_page.content = new_wiki_content
688 new_wiki_page.content = new_wiki_content
680 wiki.pages << new_wiki_page
689 wiki.pages << new_wiki_page
681 wiki_pages_map[page.id] = new_wiki_page
690 wiki_pages_map[page.id] = new_wiki_page
682 end
691 end
683 wiki.save
692 wiki.save
684 # Reproduce page hierarchy
693 # Reproduce page hierarchy
685 project.wiki.pages.each do |page|
694 project.wiki.pages.each do |page|
686 if page.parent_id && wiki_pages_map[page.id]
695 if page.parent_id && wiki_pages_map[page.id]
687 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
696 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
688 wiki_pages_map[page.id].save
697 wiki_pages_map[page.id].save
689 end
698 end
690 end
699 end
691 end
700 end
692 end
701 end
693
702
694 # Copies versions from +project+
703 # Copies versions from +project+
695 def copy_versions(project)
704 def copy_versions(project)
696 project.versions.each do |version|
705 project.versions.each do |version|
697 new_version = Version.new
706 new_version = Version.new
698 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
707 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
699 self.versions << new_version
708 self.versions << new_version
700 end
709 end
701 end
710 end
702
711
703 # Copies issue categories from +project+
712 # Copies issue categories from +project+
704 def copy_issue_categories(project)
713 def copy_issue_categories(project)
705 project.issue_categories.each do |issue_category|
714 project.issue_categories.each do |issue_category|
706 new_issue_category = IssueCategory.new
715 new_issue_category = IssueCategory.new
707 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
716 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
708 self.issue_categories << new_issue_category
717 self.issue_categories << new_issue_category
709 end
718 end
710 end
719 end
711
720
712 # Copies issues from +project+
721 # Copies issues from +project+
713 # Note: issues assigned to a closed version won't be copied due to validation rules
722 # Note: issues assigned to a closed version won't be copied due to validation rules
714 def copy_issues(project)
723 def copy_issues(project)
715 # Stores the source issue id as a key and the copied issues as the
724 # Stores the source issue id as a key and the copied issues as the
716 # value. Used to map the two togeather for issue relations.
725 # value. Used to map the two togeather for issue relations.
717 issues_map = {}
726 issues_map = {}
718
727
719 # Get issues sorted by root_id, lft so that parent issues
728 # Get issues sorted by root_id, lft so that parent issues
720 # get copied before their children
729 # get copied before their children
721 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
730 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
722 new_issue = Issue.new
731 new_issue = Issue.new
723 new_issue.copy_from(issue)
732 new_issue.copy_from(issue)
724 new_issue.project = self
733 new_issue.project = self
725 # Reassign fixed_versions by name, since names are unique per
734 # Reassign fixed_versions by name, since names are unique per
726 # project and the versions for self are not yet saved
735 # project and the versions for self are not yet saved
727 if issue.fixed_version
736 if issue.fixed_version
728 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
737 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
729 end
738 end
730 # Reassign the category by name, since names are unique per
739 # Reassign the category by name, since names are unique per
731 # project and the categories for self are not yet saved
740 # project and the categories for self are not yet saved
732 if issue.category
741 if issue.category
733 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
742 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
734 end
743 end
735 # Parent issue
744 # Parent issue
736 if issue.parent_id
745 if issue.parent_id
737 if copied_parent = issues_map[issue.parent_id]
746 if copied_parent = issues_map[issue.parent_id]
738 new_issue.parent_issue_id = copied_parent.id
747 new_issue.parent_issue_id = copied_parent.id
739 end
748 end
740 end
749 end
741
750
742 self.issues << new_issue
751 self.issues << new_issue
743 if new_issue.new_record?
752 if new_issue.new_record?
744 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
753 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
745 else
754 else
746 issues_map[issue.id] = new_issue unless new_issue.new_record?
755 issues_map[issue.id] = new_issue unless new_issue.new_record?
747 end
756 end
748 end
757 end
749
758
750 # Relations after in case issues related each other
759 # Relations after in case issues related each other
751 project.issues.each do |issue|
760 project.issues.each do |issue|
752 new_issue = issues_map[issue.id]
761 new_issue = issues_map[issue.id]
753 unless new_issue
762 unless new_issue
754 # Issue was not copied
763 # Issue was not copied
755 next
764 next
756 end
765 end
757
766
758 # Relations
767 # Relations
759 issue.relations_from.each do |source_relation|
768 issue.relations_from.each do |source_relation|
760 new_issue_relation = IssueRelation.new
769 new_issue_relation = IssueRelation.new
761 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
770 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
762 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
771 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
763 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
772 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
764 new_issue_relation.issue_to = source_relation.issue_to
773 new_issue_relation.issue_to = source_relation.issue_to
765 end
774 end
766 new_issue.relations_from << new_issue_relation
775 new_issue.relations_from << new_issue_relation
767 end
776 end
768
777
769 issue.relations_to.each do |source_relation|
778 issue.relations_to.each do |source_relation|
770 new_issue_relation = IssueRelation.new
779 new_issue_relation = IssueRelation.new
771 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
780 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
772 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
781 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
773 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
782 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
774 new_issue_relation.issue_from = source_relation.issue_from
783 new_issue_relation.issue_from = source_relation.issue_from
775 end
784 end
776 new_issue.relations_to << new_issue_relation
785 new_issue.relations_to << new_issue_relation
777 end
786 end
778 end
787 end
779 end
788 end
780
789
781 # Copies members from +project+
790 # Copies members from +project+
782 def copy_members(project)
791 def copy_members(project)
783 # Copy users first, then groups to handle members with inherited and given roles
792 # Copy users first, then groups to handle members with inherited and given roles
784 members_to_copy = []
793 members_to_copy = []
785 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
794 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
786 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
795 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
787
796
788 members_to_copy.each do |member|
797 members_to_copy.each do |member|
789 new_member = Member.new
798 new_member = Member.new
790 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
799 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
791 # only copy non inherited roles
800 # only copy non inherited roles
792 # inherited roles will be added when copying the group membership
801 # inherited roles will be added when copying the group membership
793 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
802 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
794 next if role_ids.empty?
803 next if role_ids.empty?
795 new_member.role_ids = role_ids
804 new_member.role_ids = role_ids
796 new_member.project = self
805 new_member.project = self
797 self.members << new_member
806 self.members << new_member
798 end
807 end
799 end
808 end
800
809
801 # Copies queries from +project+
810 # Copies queries from +project+
802 def copy_queries(project)
811 def copy_queries(project)
803 project.queries.each do |query|
812 project.queries.each do |query|
804 new_query = Query.new
813 new_query = Query.new
805 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
814 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
806 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
815 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
807 new_query.project = self
816 new_query.project = self
808 new_query.user_id = query.user_id
817 new_query.user_id = query.user_id
809 self.queries << new_query
818 self.queries << new_query
810 end
819 end
811 end
820 end
812
821
813 # Copies boards from +project+
822 # Copies boards from +project+
814 def copy_boards(project)
823 def copy_boards(project)
815 project.boards.each do |board|
824 project.boards.each do |board|
816 new_board = Board.new
825 new_board = Board.new
817 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
826 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
818 new_board.project = self
827 new_board.project = self
819 self.boards << new_board
828 self.boards << new_board
820 end
829 end
821 end
830 end
822
831
823 def allowed_permissions
832 def allowed_permissions
824 @allowed_permissions ||= begin
833 @allowed_permissions ||= begin
825 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
834 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
826 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
835 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
827 end
836 end
828 end
837 end
829
838
830 def allowed_actions
839 def allowed_actions
831 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
840 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
832 end
841 end
833
842
834 # Returns all the active Systemwide and project specific activities
843 # Returns all the active Systemwide and project specific activities
835 def active_activities
844 def active_activities
836 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
845 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
837
846
838 if overridden_activity_ids.empty?
847 if overridden_activity_ids.empty?
839 return TimeEntryActivity.shared.active
848 return TimeEntryActivity.shared.active
840 else
849 else
841 return system_activities_and_project_overrides
850 return system_activities_and_project_overrides
842 end
851 end
843 end
852 end
844
853
845 # Returns all the Systemwide and project specific activities
854 # Returns all the Systemwide and project specific activities
846 # (inactive and active)
855 # (inactive and active)
847 def all_activities
856 def all_activities
848 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
857 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
849
858
850 if overridden_activity_ids.empty?
859 if overridden_activity_ids.empty?
851 return TimeEntryActivity.shared
860 return TimeEntryActivity.shared
852 else
861 else
853 return system_activities_and_project_overrides(true)
862 return system_activities_and_project_overrides(true)
854 end
863 end
855 end
864 end
856
865
857 # Returns the systemwide active activities merged with the project specific overrides
866 # Returns the systemwide active activities merged with the project specific overrides
858 def system_activities_and_project_overrides(include_inactive=false)
867 def system_activities_and_project_overrides(include_inactive=false)
859 if include_inactive
868 if include_inactive
860 return TimeEntryActivity.shared.
869 return TimeEntryActivity.shared.
861 find(:all,
870 find(:all,
862 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
871 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
863 self.time_entry_activities
872 self.time_entry_activities
864 else
873 else
865 return TimeEntryActivity.shared.active.
874 return TimeEntryActivity.shared.active.
866 find(:all,
875 find(:all,
867 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
876 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
868 self.time_entry_activities.active
877 self.time_entry_activities.active
869 end
878 end
870 end
879 end
871
880
872 # Archives subprojects recursively
881 # Archives subprojects recursively
873 def archive!
882 def archive!
874 children.each do |subproject|
883 children.each do |subproject|
875 subproject.send :archive!
884 subproject.send :archive!
876 end
885 end
877 update_attribute :status, STATUS_ARCHIVED
886 update_attribute :status, STATUS_ARCHIVED
878 end
887 end
879 end
888 end
@@ -1,148 +1,157
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'admin_controller'
19 require 'admin_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class AdminController; def rescue_action(e) raise e end; end
22 class AdminController; def rescue_action(e) raise e end; end
23
23
24 class AdminControllerTest < ActionController::TestCase
24 class AdminControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles
25 fixtures :projects, :users, :roles
26
26
27 def setup
27 def setup
28 @controller = AdminController.new
28 @controller = AdminController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 @request.session[:user_id] = 1 # admin
32 @request.session[:user_id] = 1 # admin
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_no_tag :tag => 'div',
37 assert_no_tag :tag => 'div',
38 :attributes => { :class => /nodata/ }
38 :attributes => { :class => /nodata/ }
39 end
39 end
40
40
41 def test_index_with_no_configuration_data
41 def test_index_with_no_configuration_data
42 delete_configuration_data
42 delete_configuration_data
43 get :index
43 get :index
44 assert_tag :tag => 'div',
44 assert_tag :tag => 'div',
45 :attributes => { :class => /nodata/ }
45 :attributes => { :class => /nodata/ }
46 end
46 end
47
47
48 def test_projects
48 def test_projects
49 get :projects
49 get :projects
50 assert_response :success
50 assert_response :success
51 assert_template 'projects'
51 assert_template 'projects'
52 assert_not_nil assigns(:projects)
52 assert_not_nil assigns(:projects)
53 # active projects only
53 # active projects only
54 assert_nil assigns(:projects).detect {|u| !u.active?}
54 assert_nil assigns(:projects).detect {|u| !u.active?}
55 end
55 end
56
56
57 def test_projects_with_status_filter
58 get :projects, :status => 1
59 assert_response :success
60 assert_template 'projects'
61 assert_not_nil assigns(:projects)
62 # active projects only
63 assert_nil assigns(:projects).detect {|u| !u.active?}
64 end
65
57 def test_projects_with_name_filter
66 def test_projects_with_name_filter
58 get :projects, :name => 'store', :status => ''
67 get :projects, :name => 'store', :status => ''
59 assert_response :success
68 assert_response :success
60 assert_template 'projects'
69 assert_template 'projects'
61 projects = assigns(:projects)
70 projects = assigns(:projects)
62 assert_not_nil projects
71 assert_not_nil projects
63 assert_equal 1, projects.size
72 assert_equal 1, projects.size
64 assert_equal 'OnlineStore', projects.first.name
73 assert_equal 'OnlineStore', projects.first.name
65 end
74 end
66
75
67 def test_load_default_configuration_data
76 def test_load_default_configuration_data
68 delete_configuration_data
77 delete_configuration_data
69 post :default_configuration, :lang => 'fr'
78 post :default_configuration, :lang => 'fr'
70 assert_response :redirect
79 assert_response :redirect
71 assert_nil flash[:error]
80 assert_nil flash[:error]
72 assert IssueStatus.find_by_name('Nouveau')
81 assert IssueStatus.find_by_name('Nouveau')
73 end
82 end
74
83
75 def test_test_email
84 def test_test_email
76 get :test_email
85 get :test_email
77 assert_redirected_to '/settings/edit?tab=notifications'
86 assert_redirected_to '/settings/edit?tab=notifications'
78 mail = ActionMailer::Base.deliveries.last
87 mail = ActionMailer::Base.deliveries.last
79 assert_kind_of TMail::Mail, mail
88 assert_kind_of TMail::Mail, mail
80 user = User.find(1)
89 user = User.find(1)
81 assert_equal [user.mail], mail.bcc
90 assert_equal [user.mail], mail.bcc
82 end
91 end
83
92
84 def test_test_email_failure_should_display_the_error
93 def test_test_email_failure_should_display_the_error
85 Mailer.stubs(:deliver_test).raises(Exception, 'Some error message')
94 Mailer.stubs(:deliver_test).raises(Exception, 'Some error message')
86 get :test_email
95 get :test_email
87 assert_redirected_to '/settings/edit?tab=notifications'
96 assert_redirected_to '/settings/edit?tab=notifications'
88 assert_match /Some error message/, flash[:error]
97 assert_match /Some error message/, flash[:error]
89 end
98 end
90
99
91 def test_no_plugins
100 def test_no_plugins
92 Redmine::Plugin.clear
101 Redmine::Plugin.clear
93
102
94 get :plugins
103 get :plugins
95 assert_response :success
104 assert_response :success
96 assert_template 'plugins'
105 assert_template 'plugins'
97 end
106 end
98
107
99 def test_plugins
108 def test_plugins
100 # Register a few plugins
109 # Register a few plugins
101 Redmine::Plugin.register :foo do
110 Redmine::Plugin.register :foo do
102 name 'Foo plugin'
111 name 'Foo plugin'
103 author 'John Smith'
112 author 'John Smith'
104 description 'This is a test plugin'
113 description 'This is a test plugin'
105 version '0.0.1'
114 version '0.0.1'
106 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
115 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
107 end
116 end
108 Redmine::Plugin.register :bar do
117 Redmine::Plugin.register :bar do
109 end
118 end
110
119
111 get :plugins
120 get :plugins
112 assert_response :success
121 assert_response :success
113 assert_template 'plugins'
122 assert_template 'plugins'
114
123
115 assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' }
124 assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' }
116 assert_tag :td, :child => { :tag => 'span', :content => 'Bar' }
125 assert_tag :td, :child => { :tag => 'span', :content => 'Bar' }
117 end
126 end
118
127
119 def test_info
128 def test_info
120 get :info
129 get :info
121 assert_response :success
130 assert_response :success
122 assert_template 'info'
131 assert_template 'info'
123 end
132 end
124
133
125 def test_admin_menu_plugin_extension
134 def test_admin_menu_plugin_extension
126 Redmine::MenuManager.map :admin_menu do |menu|
135 Redmine::MenuManager.map :admin_menu do |menu|
127 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
136 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
128 end
137 end
129
138
130 get :index
139 get :index
131 assert_response :success
140 assert_response :success
132 assert_tag :a, :attributes => { :href => '/foo/bar' },
141 assert_tag :a, :attributes => { :href => '/foo/bar' },
133 :content => 'Test'
142 :content => 'Test'
134
143
135 Redmine::MenuManager.map :admin_menu do |menu|
144 Redmine::MenuManager.map :admin_menu do |menu|
136 menu.delete :test_admin_menu_plugin_extension
145 menu.delete :test_admin_menu_plugin_extension
137 end
146 end
138 end
147 end
139
148
140 private
149 private
141
150
142 def delete_configuration_data
151 def delete_configuration_data
143 Role.delete_all('builtin = 0')
152 Role.delete_all('builtin = 0')
144 Tracker.delete_all
153 Tracker.delete_all
145 IssueStatus.delete_all
154 IssueStatus.delete_all
146 Enumeration.delete_all
155 Enumeration.delete_all
147 end
156 end
148 end
157 end
General Comments 0
You need to be logged in to leave comments. Login now