##// END OF EJS Templates
Merged r14508 (#20512)....
Jean-Philippe Lang -
r14175:e7a3b1f0930a
parent child
Show More
@@ -1,1035 +1,1035
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 include Redmine::NestedSet::ProjectNestedSet
20 include Redmine::NestedSet::ProjectNestedSet
21
21
22 # Project statuses
22 # Project statuses
23 STATUS_ACTIVE = 1
23 STATUS_ACTIVE = 1
24 STATUS_CLOSED = 5
24 STATUS_CLOSED = 5
25 STATUS_ARCHIVED = 9
25 STATUS_ARCHIVED = 9
26
26
27 # Maximum length for project identifiers
27 # Maximum length for project identifiers
28 IDENTIFIER_MAX_LENGTH = 100
28 IDENTIFIER_MAX_LENGTH = 100
29
29
30 # Specific overridden Activities
30 # Specific overridden Activities
31 has_many :time_entry_activities
31 has_many :time_entry_activities
32 has_many :members,
32 has_many :members,
33 lambda { joins(:principal, :roles).
33 lambda { joins(:principal, :roles).
34 where("#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}") }
34 where("#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}") }
35 has_many :memberships, :class_name => 'Member'
35 has_many :memberships, :class_name => 'Member'
36 has_many :member_principals,
36 has_many :member_principals,
37 lambda { joins(:principal).
37 lambda { joins(:principal).
38 where("#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}")},
38 where("#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}")},
39 :class_name => 'Member'
39 :class_name => 'Member'
40 has_many :enabled_modules, :dependent => :delete_all
40 has_many :enabled_modules, :dependent => :delete_all
41 has_and_belongs_to_many :trackers, lambda {order(:position)}
41 has_and_belongs_to_many :trackers, lambda {order(:position)}
42 has_many :issues, :dependent => :destroy
42 has_many :issues, :dependent => :destroy
43 has_many :issue_changes, :through => :issues, :source => :journals
43 has_many :issue_changes, :through => :issues, :source => :journals
44 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
44 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
45 has_many :time_entries, :dependent => :destroy
45 has_many :time_entries, :dependent => :destroy
46 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
46 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
47 has_many :documents, :dependent => :destroy
47 has_many :documents, :dependent => :destroy
48 has_many :news, lambda {includes(:author)}, :dependent => :destroy
48 has_many :news, lambda {includes(:author)}, :dependent => :destroy
49 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
49 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
50 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
50 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
51 has_one :repository, lambda {where(["is_default = ?", true])}
51 has_one :repository, lambda {where(["is_default = ?", true])}
52 has_many :repositories, :dependent => :destroy
52 has_many :repositories, :dependent => :destroy
53 has_many :changesets, :through => :repository
53 has_many :changesets, :through => :repository
54 has_one :wiki, :dependent => :destroy
54 has_one :wiki, :dependent => :destroy
55 # Custom field for the project issues
55 # Custom field for the project issues
56 has_and_belongs_to_many :issue_custom_fields,
56 has_and_belongs_to_many :issue_custom_fields,
57 lambda {order("#{CustomField.table_name}.position")},
57 lambda {order("#{CustomField.table_name}.position")},
58 :class_name => 'IssueCustomField',
58 :class_name => 'IssueCustomField',
59 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
59 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
60 :association_foreign_key => 'custom_field_id'
60 :association_foreign_key => 'custom_field_id'
61
61
62 acts_as_attachable :view_permission => :view_files,
62 acts_as_attachable :view_permission => :view_files,
63 :edit_permission => :manage_files,
63 :edit_permission => :manage_files,
64 :delete_permission => :manage_files
64 :delete_permission => :manage_files
65
65
66 acts_as_customizable
66 acts_as_customizable
67 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
67 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
68 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
68 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
69 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
69 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
70 :author => nil
70 :author => nil
71
71
72 attr_protected :status
72 attr_protected :status
73
73
74 validates_presence_of :name, :identifier
74 validates_presence_of :name, :identifier
75 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
75 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
76 validates_length_of :name, :maximum => 255
76 validates_length_of :name, :maximum => 255
77 validates_length_of :homepage, :maximum => 255
77 validates_length_of :homepage, :maximum => 255
78 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
78 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
79 # downcase letters, digits, dashes but not digits only
79 # downcase letters, digits, dashes but not digits only
80 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
80 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
81 # reserved words
81 # reserved words
82 validates_exclusion_of :identifier, :in => %w( new )
82 validates_exclusion_of :identifier, :in => %w( new )
83 validate :validate_parent
83 validate :validate_parent
84
84
85 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
85 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
86 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
86 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
87 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
87 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
88 before_destroy :delete_all_members
88 before_destroy :delete_all_members
89
89
90 scope :has_module, lambda {|mod|
90 scope :has_module, lambda {|mod|
91 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
91 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
92 }
92 }
93 scope :active, lambda { where(:status => STATUS_ACTIVE) }
93 scope :active, lambda { where(:status => STATUS_ACTIVE) }
94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
95 scope :all_public, lambda { where(:is_public => true) }
95 scope :all_public, lambda { where(:is_public => true) }
96 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
96 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
97 scope :allowed_to, lambda {|*args|
97 scope :allowed_to, lambda {|*args|
98 user = User.current
98 user = User.current
99 permission = nil
99 permission = nil
100 if args.first.is_a?(Symbol)
100 if args.first.is_a?(Symbol)
101 permission = args.shift
101 permission = args.shift
102 else
102 else
103 user = args.shift
103 user = args.shift
104 permission = args.shift
104 permission = args.shift
105 end
105 end
106 where(Project.allowed_to_condition(user, permission, *args))
106 where(Project.allowed_to_condition(user, permission, *args))
107 }
107 }
108 scope :like, lambda {|arg|
108 scope :like, lambda {|arg|
109 if arg.blank?
109 if arg.blank?
110 where(nil)
110 where(nil)
111 else
111 else
112 pattern = "%#{arg.to_s.strip.downcase}%"
112 pattern = "%#{arg.to_s.strip.downcase}%"
113 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
113 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
114 end
114 end
115 }
115 }
116 scope :sorted, lambda {order(:lft)}
116 scope :sorted, lambda {order(:lft)}
117
117
118 def initialize(attributes=nil, *args)
118 def initialize(attributes=nil, *args)
119 super
119 super
120
120
121 initialized = (attributes || {}).stringify_keys
121 initialized = (attributes || {}).stringify_keys
122 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
122 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
123 self.identifier = Project.next_identifier
123 self.identifier = Project.next_identifier
124 end
124 end
125 if !initialized.key?('is_public')
125 if !initialized.key?('is_public')
126 self.is_public = Setting.default_projects_public?
126 self.is_public = Setting.default_projects_public?
127 end
127 end
128 if !initialized.key?('enabled_module_names')
128 if !initialized.key?('enabled_module_names')
129 self.enabled_module_names = Setting.default_projects_modules
129 self.enabled_module_names = Setting.default_projects_modules
130 end
130 end
131 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
131 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
132 default = Setting.default_projects_tracker_ids
132 default = Setting.default_projects_tracker_ids
133 if default.is_a?(Array)
133 if default.is_a?(Array)
134 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
134 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
135 else
135 else
136 self.trackers = Tracker.sorted.to_a
136 self.trackers = Tracker.sorted.to_a
137 end
137 end
138 end
138 end
139 end
139 end
140
140
141 def identifier=(identifier)
141 def identifier=(identifier)
142 super unless identifier_frozen?
142 super unless identifier_frozen?
143 end
143 end
144
144
145 def identifier_frozen?
145 def identifier_frozen?
146 errors[:identifier].blank? && !(new_record? || identifier.blank?)
146 errors[:identifier].blank? && !(new_record? || identifier.blank?)
147 end
147 end
148
148
149 # returns latest created projects
149 # returns latest created projects
150 # non public projects will be returned only if user is a member of those
150 # non public projects will be returned only if user is a member of those
151 def self.latest(user=nil, count=5)
151 def self.latest(user=nil, count=5)
152 visible(user).limit(count).order("created_on DESC").to_a
152 visible(user).limit(count).order("created_on DESC").to_a
153 end
153 end
154
154
155 # Returns true if the project is visible to +user+ or to the current user.
155 # Returns true if the project is visible to +user+ or to the current user.
156 def visible?(user=User.current)
156 def visible?(user=User.current)
157 user.allowed_to?(:view_project, self)
157 user.allowed_to?(:view_project, self)
158 end
158 end
159
159
160 # Returns a SQL conditions string used to find all projects visible by the specified user.
160 # Returns a SQL conditions string used to find all projects visible by the specified user.
161 #
161 #
162 # Examples:
162 # Examples:
163 # Project.visible_condition(admin) => "projects.status = 1"
163 # Project.visible_condition(admin) => "projects.status = 1"
164 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
164 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
165 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
165 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
166 def self.visible_condition(user, options={})
166 def self.visible_condition(user, options={})
167 allowed_to_condition(user, :view_project, options)
167 allowed_to_condition(user, :view_project, options)
168 end
168 end
169
169
170 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
170 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
171 #
171 #
172 # Valid options:
172 # Valid options:
173 # * :project => limit the condition to project
173 # * :project => limit the condition to project
174 # * :with_subprojects => limit the condition to project and its subprojects
174 # * :with_subprojects => limit the condition to project and its subprojects
175 # * :member => limit the condition to the user projects
175 # * :member => limit the condition to the user projects
176 def self.allowed_to_condition(user, permission, options={})
176 def self.allowed_to_condition(user, permission, options={})
177 perm = Redmine::AccessControl.permission(permission)
177 perm = Redmine::AccessControl.permission(permission)
178 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
178 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
179 if perm && perm.project_module
179 if perm && perm.project_module
180 # If the permission belongs to a project module, make sure the module is enabled
180 # If the permission belongs to a project module, make sure the module is enabled
181 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
181 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
182 end
182 end
183 if project = options[:project]
183 if project = options[:project]
184 project_statement = project.project_condition(options[:with_subprojects])
184 project_statement = project.project_condition(options[:with_subprojects])
185 base_statement = "(#{project_statement}) AND (#{base_statement})"
185 base_statement = "(#{project_statement}) AND (#{base_statement})"
186 end
186 end
187
187
188 if user.admin?
188 if user.admin?
189 base_statement
189 base_statement
190 else
190 else
191 statement_by_role = {}
191 statement_by_role = {}
192 unless options[:member]
192 unless options[:member]
193 role = user.builtin_role
193 role = user.builtin_role
194 if role.allowed_to?(permission)
194 if role.allowed_to?(permission)
195 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
195 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
196 end
196 end
197 end
197 end
198 user.projects_by_role.each do |role, projects|
198 user.projects_by_role.each do |role, projects|
199 if role.allowed_to?(permission) && projects.any?
199 if role.allowed_to?(permission) && projects.any?
200 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
200 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
201 end
201 end
202 end
202 end
203 if statement_by_role.empty?
203 if statement_by_role.empty?
204 "1=0"
204 "1=0"
205 else
205 else
206 if block_given?
206 if block_given?
207 statement_by_role.each do |role, statement|
207 statement_by_role.each do |role, statement|
208 if s = yield(role, user)
208 if s = yield(role, user)
209 statement_by_role[role] = "(#{statement} AND (#{s}))"
209 statement_by_role[role] = "(#{statement} AND (#{s}))"
210 end
210 end
211 end
211 end
212 end
212 end
213 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
213 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
214 end
214 end
215 end
215 end
216 end
216 end
217
217
218 def override_roles(role)
218 def override_roles(role)
219 @override_members ||= member_principals.
219 @override_members ||= member_principals.
220 where("#{Principal.table_name}.type IN (?)", ['GroupAnonymous', 'GroupNonMember']).to_a
220 where("#{Principal.table_name}.type IN (?)", ['GroupAnonymous', 'GroupNonMember']).to_a
221
221
222 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
222 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
223 member = @override_members.detect {|m| m.principal.is_a? group_class}
223 member = @override_members.detect {|m| m.principal.is_a? group_class}
224 member ? member.roles.to_a : [role]
224 member ? member.roles.to_a : [role]
225 end
225 end
226
226
227 def principals
227 def principals
228 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
228 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
229 end
229 end
230
230
231 def users
231 def users
232 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
232 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
233 end
233 end
234
234
235 # Returns the Systemwide and project specific activities
235 # Returns the Systemwide and project specific activities
236 def activities(include_inactive=false)
236 def activities(include_inactive=false)
237 if include_inactive
237 if include_inactive
238 return all_activities
238 return all_activities
239 else
239 else
240 return active_activities
240 return active_activities
241 end
241 end
242 end
242 end
243
243
244 # Will create a new Project specific Activity or update an existing one
244 # Will create a new Project specific Activity or update an existing one
245 #
245 #
246 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
246 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
247 # does not successfully save.
247 # does not successfully save.
248 def update_or_create_time_entry_activity(id, activity_hash)
248 def update_or_create_time_entry_activity(id, activity_hash)
249 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
249 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
250 self.create_time_entry_activity_if_needed(activity_hash)
250 self.create_time_entry_activity_if_needed(activity_hash)
251 else
251 else
252 activity = project.time_entry_activities.find_by_id(id.to_i)
252 activity = project.time_entry_activities.find_by_id(id.to_i)
253 activity.update_attributes(activity_hash) if activity
253 activity.update_attributes(activity_hash) if activity
254 end
254 end
255 end
255 end
256
256
257 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
257 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
258 #
258 #
259 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
259 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
260 # does not successfully save.
260 # does not successfully save.
261 def create_time_entry_activity_if_needed(activity)
261 def create_time_entry_activity_if_needed(activity)
262 if activity['parent_id']
262 if activity['parent_id']
263 parent_activity = TimeEntryActivity.find(activity['parent_id'])
263 parent_activity = TimeEntryActivity.find(activity['parent_id'])
264 activity['name'] = parent_activity.name
264 activity['name'] = parent_activity.name
265 activity['position'] = parent_activity.position
265 activity['position'] = parent_activity.position
266 if Enumeration.overriding_change?(activity, parent_activity)
266 if Enumeration.overriding_change?(activity, parent_activity)
267 project_activity = self.time_entry_activities.create(activity)
267 project_activity = self.time_entry_activities.create(activity)
268 if project_activity.new_record?
268 if project_activity.new_record?
269 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
269 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
270 else
270 else
271 self.time_entries.
271 self.time_entries.
272 where(["activity_id = ?", parent_activity.id]).
272 where(["activity_id = ?", parent_activity.id]).
273 update_all("activity_id = #{project_activity.id}")
273 update_all("activity_id = #{project_activity.id}")
274 end
274 end
275 end
275 end
276 end
276 end
277 end
277 end
278
278
279 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
279 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
280 #
280 #
281 # Examples:
281 # Examples:
282 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
282 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
283 # project.project_condition(false) => "projects.id = 1"
283 # project.project_condition(false) => "projects.id = 1"
284 def project_condition(with_subprojects)
284 def project_condition(with_subprojects)
285 cond = "#{Project.table_name}.id = #{id}"
285 cond = "#{Project.table_name}.id = #{id}"
286 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
286 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
287 cond
287 cond
288 end
288 end
289
289
290 def self.find(*args)
290 def self.find(*args)
291 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
291 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
292 project = find_by_identifier(*args)
292 project = find_by_identifier(*args)
293 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
293 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
294 project
294 project
295 else
295 else
296 super
296 super
297 end
297 end
298 end
298 end
299
299
300 def self.find_by_param(*args)
300 def self.find_by_param(*args)
301 self.find(*args)
301 self.find(*args)
302 end
302 end
303
303
304 alias :base_reload :reload
304 alias :base_reload :reload
305 def reload(*args)
305 def reload(*args)
306 @principals = nil
306 @principals = nil
307 @users = nil
307 @users = nil
308 @shared_versions = nil
308 @shared_versions = nil
309 @rolled_up_versions = nil
309 @rolled_up_versions = nil
310 @rolled_up_trackers = nil
310 @rolled_up_trackers = nil
311 @all_issue_custom_fields = nil
311 @all_issue_custom_fields = nil
312 @all_time_entry_custom_fields = nil
312 @all_time_entry_custom_fields = nil
313 @to_param = nil
313 @to_param = nil
314 @allowed_parents = nil
314 @allowed_parents = nil
315 @allowed_permissions = nil
315 @allowed_permissions = nil
316 @actions_allowed = nil
316 @actions_allowed = nil
317 @start_date = nil
317 @start_date = nil
318 @due_date = nil
318 @due_date = nil
319 @override_members = nil
319 @override_members = nil
320 @assignable_users = nil
320 @assignable_users = nil
321 base_reload(*args)
321 base_reload(*args)
322 end
322 end
323
323
324 def to_param
324 def to_param
325 # id is used for projects with a numeric identifier (compatibility)
325 # id is used for projects with a numeric identifier (compatibility)
326 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
326 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
327 end
327 end
328
328
329 def active?
329 def active?
330 self.status == STATUS_ACTIVE
330 self.status == STATUS_ACTIVE
331 end
331 end
332
332
333 def archived?
333 def archived?
334 self.status == STATUS_ARCHIVED
334 self.status == STATUS_ARCHIVED
335 end
335 end
336
336
337 # Archives the project and its descendants
337 # Archives the project and its descendants
338 def archive
338 def archive
339 # Check that there is no issue of a non descendant project that is assigned
339 # Check that there is no issue of a non descendant project that is assigned
340 # to one of the project or descendant versions
340 # to one of the project or descendant versions
341 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
341 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
342
342
343 if version_ids.any? &&
343 if version_ids.any? &&
344 Issue.
344 Issue.
345 includes(:project).
345 includes(:project).
346 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
346 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
347 where(:fixed_version_id => version_ids).
347 where(:fixed_version_id => version_ids).
348 exists?
348 exists?
349 return false
349 return false
350 end
350 end
351 Project.transaction do
351 Project.transaction do
352 archive!
352 archive!
353 end
353 end
354 true
354 true
355 end
355 end
356
356
357 # Unarchives the project
357 # Unarchives the project
358 # All its ancestors must be active
358 # All its ancestors must be active
359 def unarchive
359 def unarchive
360 return false if ancestors.detect {|a| !a.active?}
360 return false if ancestors.detect {|a| !a.active?}
361 update_attribute :status, STATUS_ACTIVE
361 update_attribute :status, STATUS_ACTIVE
362 end
362 end
363
363
364 def close
364 def close
365 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
365 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
366 end
366 end
367
367
368 def reopen
368 def reopen
369 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
369 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
370 end
370 end
371
371
372 # Returns an array of projects the project can be moved to
372 # Returns an array of projects the project can be moved to
373 # by the current user
373 # by the current user
374 def allowed_parents(user=User.current)
374 def allowed_parents(user=User.current)
375 return @allowed_parents if @allowed_parents
375 return @allowed_parents if @allowed_parents
376 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
376 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
377 @allowed_parents = @allowed_parents - self_and_descendants
377 @allowed_parents = @allowed_parents - self_and_descendants
378 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
378 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
379 @allowed_parents << nil
379 @allowed_parents << nil
380 end
380 end
381 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
381 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
382 @allowed_parents << parent
382 @allowed_parents << parent
383 end
383 end
384 @allowed_parents
384 @allowed_parents
385 end
385 end
386
386
387 # Sets the parent of the project with authorization check
387 # Sets the parent of the project with authorization check
388 def set_allowed_parent!(p)
388 def set_allowed_parent!(p)
389 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
389 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
390 p = p.id if p.is_a?(Project)
390 p = p.id if p.is_a?(Project)
391 send :safe_attributes, {:project_id => p}
391 send :safe_attributes, {:project_id => p}
392 save
392 save
393 end
393 end
394
394
395 # Sets the parent of the project and saves the project
395 # Sets the parent of the project and saves the project
396 # Argument can be either a Project, a String, a Fixnum or nil
396 # Argument can be either a Project, a String, a Fixnum or nil
397 def set_parent!(p)
397 def set_parent!(p)
398 if p.is_a?(Project)
398 if p.is_a?(Project)
399 self.parent = p
399 self.parent = p
400 else
400 else
401 self.parent_id = p
401 self.parent_id = p
402 end
402 end
403 save
403 save
404 end
404 end
405
405
406 # Returns an array of the trackers used by the project and its active sub projects
406 # Returns an array of the trackers used by the project and its active sub projects
407 def rolled_up_trackers
407 def rolled_up_trackers
408 @rolled_up_trackers ||=
408 @rolled_up_trackers ||=
409 Tracker.
409 Tracker.
410 joins(:projects).
410 joins(:projects).
411 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
411 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
412 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED).
412 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED).
413 uniq.
413 uniq.
414 sorted.
414 sorted.
415 to_a
415 to_a
416 end
416 end
417
417
418 # Closes open and locked project versions that are completed
418 # Closes open and locked project versions that are completed
419 def close_completed_versions
419 def close_completed_versions
420 Version.transaction do
420 Version.transaction do
421 versions.where(:status => %w(open locked)).each do |version|
421 versions.where(:status => %w(open locked)).each do |version|
422 if version.completed?
422 if version.completed?
423 version.update_attribute(:status, 'closed')
423 version.update_attribute(:status, 'closed')
424 end
424 end
425 end
425 end
426 end
426 end
427 end
427 end
428
428
429 # Returns a scope of the Versions on subprojects
429 # Returns a scope of the Versions on subprojects
430 def rolled_up_versions
430 def rolled_up_versions
431 @rolled_up_versions ||=
431 @rolled_up_versions ||=
432 Version.
432 Version.
433 joins(:project).
433 joins(:project).
434 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
434 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
435 end
435 end
436
436
437 # Returns a scope of the Versions used by the project
437 # Returns a scope of the Versions used by the project
438 def shared_versions
438 def shared_versions
439 if new_record?
439 if new_record?
440 Version.
440 Version.
441 joins(:project).
441 joins(:project).
442 preload(:project).
442 preload(:project).
443 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
443 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
444 else
444 else
445 @shared_versions ||= begin
445 @shared_versions ||= begin
446 r = root? ? self : root
446 r = root? ? self : root
447 Version.
447 Version.
448 joins(:project).
448 joins(:project).
449 preload(:project).
449 preload(:project).
450 where("#{Project.table_name}.id = #{id}" +
450 where("#{Project.table_name}.id = #{id}" +
451 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
451 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
452 " #{Version.table_name}.sharing = 'system'" +
452 " #{Version.table_name}.sharing = 'system'" +
453 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
453 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
454 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
454 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
455 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
455 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
456 "))")
456 "))")
457 end
457 end
458 end
458 end
459 end
459 end
460
460
461 # Returns a hash of project users grouped by role
461 # Returns a hash of project users grouped by role
462 def users_by_role
462 def users_by_role
463 members.includes(:user, :roles).inject({}) do |h, m|
463 members.includes(:user, :roles).inject({}) do |h, m|
464 m.roles.each do |r|
464 m.roles.each do |r|
465 h[r] ||= []
465 h[r] ||= []
466 h[r] << m.user
466 h[r] << m.user
467 end
467 end
468 h
468 h
469 end
469 end
470 end
470 end
471
471
472 # Adds user as a project member with the default role
472 # Adds user as a project member with the default role
473 # Used for when a non-admin user creates a project
473 # Used for when a non-admin user creates a project
474 def add_default_member(user)
474 def add_default_member(user)
475 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
475 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
476 member = Member.new(:project => self, :principal => user, :roles => [role])
476 member = Member.new(:project => self, :principal => user, :roles => [role])
477 self.members << member
477 self.members << member
478 member
478 member
479 end
479 end
480
480
481 # Deletes all project's members
481 # Deletes all project's members
482 def delete_all_members
482 def delete_all_members
483 me, mr = Member.table_name, MemberRole.table_name
483 me, mr = Member.table_name, MemberRole.table_name
484 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
484 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
485 Member.delete_all(['project_id = ?', id])
485 Member.delete_all(['project_id = ?', id])
486 end
486 end
487
487
488 # Return a Principal scope of users/groups issues can be assigned to
488 # Return a Principal scope of users/groups issues can be assigned to
489 def assignable_users
489 def assignable_users
490 types = ['User']
490 types = ['User']
491 types << 'Group' if Setting.issue_group_assignment?
491 types << 'Group' if Setting.issue_group_assignment?
492
492
493 @assignable_users ||= Principal.
493 @assignable_users ||= Principal.
494 active.
494 active.
495 joins(:members => :roles).
495 joins(:members => :roles).
496 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
496 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
497 uniq.
497 uniq.
498 sorted
498 sorted
499 end
499 end
500
500
501 # Returns the mail addresses of users that should be always notified on project events
501 # Returns the mail addresses of users that should be always notified on project events
502 def recipients
502 def recipients
503 notified_users.collect {|user| user.mail}
503 notified_users.collect {|user| user.mail}
504 end
504 end
505
505
506 # Returns the users that should be notified on project events
506 # Returns the users that should be notified on project events
507 def notified_users
507 def notified_users
508 # TODO: User part should be extracted to User#notify_about?
508 # TODO: User part should be extracted to User#notify_about?
509 members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
509 members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
510 end
510 end
511
511
512 # Returns a scope of all custom fields enabled for project issues
512 # Returns a scope of all custom fields enabled for project issues
513 # (explicitly associated custom fields and custom fields enabled for all projects)
513 # (explicitly associated custom fields and custom fields enabled for all projects)
514 def all_issue_custom_fields
514 def all_issue_custom_fields
515 @all_issue_custom_fields ||= IssueCustomField.
515 @all_issue_custom_fields ||= IssueCustomField.
516 sorted.
516 sorted.
517 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
517 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
518 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
518 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
519 " WHERE cfp.project_id = ?)", true, id)
519 " WHERE cfp.project_id = ?)", true, id)
520 end
520 end
521
521
522 def project
522 def project
523 self
523 self
524 end
524 end
525
525
526 def <=>(project)
526 def <=>(project)
527 name.downcase <=> project.name.downcase
527 name.downcase <=> project.name.downcase
528 end
528 end
529
529
530 def to_s
530 def to_s
531 name
531 name
532 end
532 end
533
533
534 # Returns a short description of the projects (first lines)
534 # Returns a short description of the projects (first lines)
535 def short_description(length = 255)
535 def short_description(length = 255)
536 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
536 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
537 end
537 end
538
538
539 def css_classes
539 def css_classes
540 s = 'project'
540 s = 'project'
541 s << ' root' if root?
541 s << ' root' if root?
542 s << ' child' if child?
542 s << ' child' if child?
543 s << (leaf? ? ' leaf' : ' parent')
543 s << (leaf? ? ' leaf' : ' parent')
544 unless active?
544 unless active?
545 if archived?
545 if archived?
546 s << ' archived'
546 s << ' archived'
547 else
547 else
548 s << ' closed'
548 s << ' closed'
549 end
549 end
550 end
550 end
551 s
551 s
552 end
552 end
553
553
554 # The earliest start date of a project, based on it's issues and versions
554 # The earliest start date of a project, based on it's issues and versions
555 def start_date
555 def start_date
556 @start_date ||= [
556 @start_date ||= [
557 issues.minimum('start_date'),
557 issues.minimum('start_date'),
558 shared_versions.minimum('effective_date'),
558 shared_versions.minimum('effective_date'),
559 Issue.fixed_version(shared_versions).minimum('start_date')
559 Issue.fixed_version(shared_versions).minimum('start_date')
560 ].compact.min
560 ].compact.min
561 end
561 end
562
562
563 # The latest due date of an issue or version
563 # The latest due date of an issue or version
564 def due_date
564 def due_date
565 @due_date ||= [
565 @due_date ||= [
566 issues.maximum('due_date'),
566 issues.maximum('due_date'),
567 shared_versions.maximum('effective_date'),
567 shared_versions.maximum('effective_date'),
568 Issue.fixed_version(shared_versions).maximum('due_date')
568 Issue.fixed_version(shared_versions).maximum('due_date')
569 ].compact.max
569 ].compact.max
570 end
570 end
571
571
572 def overdue?
572 def overdue?
573 active? && !due_date.nil? && (due_date < Date.today)
573 active? && !due_date.nil? && (due_date < Date.today)
574 end
574 end
575
575
576 # Returns the percent completed for this project, based on the
576 # Returns the percent completed for this project, based on the
577 # progress on it's versions.
577 # progress on it's versions.
578 def completed_percent(options={:include_subprojects => false})
578 def completed_percent(options={:include_subprojects => false})
579 if options.delete(:include_subprojects)
579 if options.delete(:include_subprojects)
580 total = self_and_descendants.collect(&:completed_percent).sum
580 total = self_and_descendants.collect(&:completed_percent).sum
581
581
582 total / self_and_descendants.count
582 total / self_and_descendants.count
583 else
583 else
584 if versions.count > 0
584 if versions.count > 0
585 total = versions.collect(&:completed_percent).sum
585 total = versions.collect(&:completed_percent).sum
586
586
587 total / versions.count
587 total / versions.count
588 else
588 else
589 100
589 100
590 end
590 end
591 end
591 end
592 end
592 end
593
593
594 # Return true if this project allows to do the specified action.
594 # Return true if this project allows to do the specified action.
595 # action can be:
595 # action can be:
596 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
596 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
597 # * a permission Symbol (eg. :edit_project)
597 # * a permission Symbol (eg. :edit_project)
598 def allows_to?(action)
598 def allows_to?(action)
599 if archived?
599 if archived?
600 # No action allowed on archived projects
600 # No action allowed on archived projects
601 return false
601 return false
602 end
602 end
603 unless active? || Redmine::AccessControl.read_action?(action)
603 unless active? || Redmine::AccessControl.read_action?(action)
604 # No write action allowed on closed projects
604 # No write action allowed on closed projects
605 return false
605 return false
606 end
606 end
607 # No action allowed on disabled modules
607 # No action allowed on disabled modules
608 if action.is_a? Hash
608 if action.is_a? Hash
609 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
609 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
610 else
610 else
611 allowed_permissions.include? action
611 allowed_permissions.include? action
612 end
612 end
613 end
613 end
614
614
615 # Return the enabled module with the given name
615 # Return the enabled module with the given name
616 # or nil if the module is not enabled for the project
616 # or nil if the module is not enabled for the project
617 def enabled_module(name)
617 def enabled_module(name)
618 name = name.to_s
618 name = name.to_s
619 enabled_modules.detect {|m| m.name == name}
619 enabled_modules.detect {|m| m.name == name}
620 end
620 end
621
621
622 # Return true if the module with the given name is enabled
622 # Return true if the module with the given name is enabled
623 def module_enabled?(name)
623 def module_enabled?(name)
624 enabled_module(name).present?
624 enabled_module(name).present?
625 end
625 end
626
626
627 def enabled_module_names=(module_names)
627 def enabled_module_names=(module_names)
628 if module_names && module_names.is_a?(Array)
628 if module_names && module_names.is_a?(Array)
629 module_names = module_names.collect(&:to_s).reject(&:blank?)
629 module_names = module_names.collect(&:to_s).reject(&:blank?)
630 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
630 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
631 else
631 else
632 enabled_modules.clear
632 enabled_modules.clear
633 end
633 end
634 end
634 end
635
635
636 # Returns an array of the enabled modules names
636 # Returns an array of the enabled modules names
637 def enabled_module_names
637 def enabled_module_names
638 enabled_modules.collect(&:name)
638 enabled_modules.collect(&:name)
639 end
639 end
640
640
641 # Enable a specific module
641 # Enable a specific module
642 #
642 #
643 # Examples:
643 # Examples:
644 # project.enable_module!(:issue_tracking)
644 # project.enable_module!(:issue_tracking)
645 # project.enable_module!("issue_tracking")
645 # project.enable_module!("issue_tracking")
646 def enable_module!(name)
646 def enable_module!(name)
647 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
647 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
648 end
648 end
649
649
650 # Disable a module if it exists
650 # Disable a module if it exists
651 #
651 #
652 # Examples:
652 # Examples:
653 # project.disable_module!(:issue_tracking)
653 # project.disable_module!(:issue_tracking)
654 # project.disable_module!("issue_tracking")
654 # project.disable_module!("issue_tracking")
655 # project.disable_module!(project.enabled_modules.first)
655 # project.disable_module!(project.enabled_modules.first)
656 def disable_module!(target)
656 def disable_module!(target)
657 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
657 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
658 target.destroy unless target.blank?
658 target.destroy unless target.blank?
659 end
659 end
660
660
661 safe_attributes 'name',
661 safe_attributes 'name',
662 'description',
662 'description',
663 'homepage',
663 'homepage',
664 'is_public',
664 'is_public',
665 'identifier',
665 'identifier',
666 'custom_field_values',
666 'custom_field_values',
667 'custom_fields',
667 'custom_fields',
668 'tracker_ids',
668 'tracker_ids',
669 'issue_custom_field_ids',
669 'issue_custom_field_ids',
670 'parent_id'
670 'parent_id'
671
671
672 safe_attributes 'enabled_module_names',
672 safe_attributes 'enabled_module_names',
673 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
673 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
674
674
675 safe_attributes 'inherit_members',
675 safe_attributes 'inherit_members',
676 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
676 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
677
677
678 def safe_attributes=(attrs, user=User.current)
678 def safe_attributes=(attrs, user=User.current)
679 return unless attrs.is_a?(Hash)
679 return unless attrs.is_a?(Hash)
680 attrs = attrs.deep_dup
680 attrs = attrs.deep_dup
681
681
682 @unallowed_parent_id = nil
682 @unallowed_parent_id = nil
683 parent_id_param = attrs['parent_id'].to_s
683 parent_id_param = attrs['parent_id'].to_s
684 if parent_id_param.blank? || parent_id_param != parent_id.to_s
684 if parent_id_param.blank? || parent_id_param != parent_id.to_s
685 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
685 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
686 unless allowed_parents(user).include?(p)
686 unless allowed_parents(user).include?(p)
687 attrs.delete('parent_id')
687 attrs.delete('parent_id')
688 @unallowed_parent_id = true
688 @unallowed_parent_id = true
689 end
689 end
690 end
690 end
691
691
692 super(attrs, user)
692 super(attrs, user)
693 end
693 end
694
694
695 # Returns an auto-generated project identifier based on the last identifier used
695 # Returns an auto-generated project identifier based on the last identifier used
696 def self.next_identifier
696 def self.next_identifier
697 p = Project.order('id DESC').first
697 p = Project.order('id DESC').first
698 p.nil? ? nil : p.identifier.to_s.succ
698 p.nil? ? nil : p.identifier.to_s.succ
699 end
699 end
700
700
701 # Copies and saves the Project instance based on the +project+.
701 # Copies and saves the Project instance based on the +project+.
702 # Duplicates the source project's:
702 # Duplicates the source project's:
703 # * Wiki
703 # * Wiki
704 # * Versions
704 # * Versions
705 # * Categories
705 # * Categories
706 # * Issues
706 # * Issues
707 # * Members
707 # * Members
708 # * Queries
708 # * Queries
709 #
709 #
710 # Accepts an +options+ argument to specify what to copy
710 # Accepts an +options+ argument to specify what to copy
711 #
711 #
712 # Examples:
712 # Examples:
713 # project.copy(1) # => copies everything
713 # project.copy(1) # => copies everything
714 # project.copy(1, :only => 'members') # => copies members only
714 # project.copy(1, :only => 'members') # => copies members only
715 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
715 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
716 def copy(project, options={})
716 def copy(project, options={})
717 project = project.is_a?(Project) ? project : Project.find(project)
717 project = project.is_a?(Project) ? project : Project.find(project)
718
718
719 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
719 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
720 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
720 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
721
721
722 Project.transaction do
722 Project.transaction do
723 if save
723 if save
724 reload
724 reload
725 to_be_copied.each do |name|
725 to_be_copied.each do |name|
726 send "copy_#{name}", project
726 send "copy_#{name}", project
727 end
727 end
728 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
728 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
729 save
729 save
730 else
730 else
731 false
731 false
732 end
732 end
733 end
733 end
734 end
734 end
735
735
736 # Returns a new unsaved Project instance with attributes copied from +project+
736 # Returns a new unsaved Project instance with attributes copied from +project+
737 def self.copy_from(project)
737 def self.copy_from(project)
738 project = project.is_a?(Project) ? project : Project.find(project)
738 project = project.is_a?(Project) ? project : Project.find(project)
739 # clear unique attributes
739 # clear unique attributes
740 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
740 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
741 copy = Project.new(attributes)
741 copy = Project.new(attributes)
742 copy.enabled_modules = project.enabled_modules
742 copy.enabled_module_names = project.enabled_module_names
743 copy.trackers = project.trackers
743 copy.trackers = project.trackers
744 copy.custom_values = project.custom_values.collect {|v| v.clone}
744 copy.custom_values = project.custom_values.collect {|v| v.clone}
745 copy.issue_custom_fields = project.issue_custom_fields
745 copy.issue_custom_fields = project.issue_custom_fields
746 copy
746 copy
747 end
747 end
748
748
749 # Yields the given block for each project with its level in the tree
749 # Yields the given block for each project with its level in the tree
750 def self.project_tree(projects, &block)
750 def self.project_tree(projects, &block)
751 ancestors = []
751 ancestors = []
752 projects.sort_by(&:lft).each do |project|
752 projects.sort_by(&:lft).each do |project|
753 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
753 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
754 ancestors.pop
754 ancestors.pop
755 end
755 end
756 yield project, ancestors.size
756 yield project, ancestors.size
757 ancestors << project
757 ancestors << project
758 end
758 end
759 end
759 end
760
760
761 private
761 private
762
762
763 def update_inherited_members
763 def update_inherited_members
764 if parent
764 if parent
765 if inherit_members? && !inherit_members_was
765 if inherit_members? && !inherit_members_was
766 remove_inherited_member_roles
766 remove_inherited_member_roles
767 add_inherited_member_roles
767 add_inherited_member_roles
768 elsif !inherit_members? && inherit_members_was
768 elsif !inherit_members? && inherit_members_was
769 remove_inherited_member_roles
769 remove_inherited_member_roles
770 end
770 end
771 end
771 end
772 end
772 end
773
773
774 def remove_inherited_member_roles
774 def remove_inherited_member_roles
775 member_roles = memberships.map(&:member_roles).flatten
775 member_roles = memberships.map(&:member_roles).flatten
776 member_role_ids = member_roles.map(&:id)
776 member_role_ids = member_roles.map(&:id)
777 member_roles.each do |member_role|
777 member_roles.each do |member_role|
778 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
778 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
779 member_role.destroy
779 member_role.destroy
780 end
780 end
781 end
781 end
782 end
782 end
783
783
784 def add_inherited_member_roles
784 def add_inherited_member_roles
785 if inherit_members? && parent
785 if inherit_members? && parent
786 parent.memberships.each do |parent_member|
786 parent.memberships.each do |parent_member|
787 member = Member.find_or_new(self.id, parent_member.user_id)
787 member = Member.find_or_new(self.id, parent_member.user_id)
788 parent_member.member_roles.each do |parent_member_role|
788 parent_member.member_roles.each do |parent_member_role|
789 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
789 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
790 end
790 end
791 member.save!
791 member.save!
792 end
792 end
793 memberships.reset
793 memberships.reset
794 end
794 end
795 end
795 end
796
796
797 def update_versions_from_hierarchy_change
797 def update_versions_from_hierarchy_change
798 Issue.update_versions_from_hierarchy_change(self)
798 Issue.update_versions_from_hierarchy_change(self)
799 end
799 end
800
800
801 def validate_parent
801 def validate_parent
802 if @unallowed_parent_id
802 if @unallowed_parent_id
803 errors.add(:parent_id, :invalid)
803 errors.add(:parent_id, :invalid)
804 elsif parent_id_changed?
804 elsif parent_id_changed?
805 unless parent.nil? || (parent.active? && move_possible?(parent))
805 unless parent.nil? || (parent.active? && move_possible?(parent))
806 errors.add(:parent_id, :invalid)
806 errors.add(:parent_id, :invalid)
807 end
807 end
808 end
808 end
809 end
809 end
810
810
811 # Copies wiki from +project+
811 # Copies wiki from +project+
812 def copy_wiki(project)
812 def copy_wiki(project)
813 # Check that the source project has a wiki first
813 # Check that the source project has a wiki first
814 unless project.wiki.nil?
814 unless project.wiki.nil?
815 wiki = self.wiki || Wiki.new
815 wiki = self.wiki || Wiki.new
816 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
816 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
817 wiki_pages_map = {}
817 wiki_pages_map = {}
818 project.wiki.pages.each do |page|
818 project.wiki.pages.each do |page|
819 # Skip pages without content
819 # Skip pages without content
820 next if page.content.nil?
820 next if page.content.nil?
821 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
821 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
822 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
822 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
823 new_wiki_page.content = new_wiki_content
823 new_wiki_page.content = new_wiki_content
824 wiki.pages << new_wiki_page
824 wiki.pages << new_wiki_page
825 wiki_pages_map[page.id] = new_wiki_page
825 wiki_pages_map[page.id] = new_wiki_page
826 end
826 end
827
827
828 self.wiki = wiki
828 self.wiki = wiki
829 wiki.save
829 wiki.save
830 # Reproduce page hierarchy
830 # Reproduce page hierarchy
831 project.wiki.pages.each do |page|
831 project.wiki.pages.each do |page|
832 if page.parent_id && wiki_pages_map[page.id]
832 if page.parent_id && wiki_pages_map[page.id]
833 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
833 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
834 wiki_pages_map[page.id].save
834 wiki_pages_map[page.id].save
835 end
835 end
836 end
836 end
837 end
837 end
838 end
838 end
839
839
840 # Copies versions from +project+
840 # Copies versions from +project+
841 def copy_versions(project)
841 def copy_versions(project)
842 project.versions.each do |version|
842 project.versions.each do |version|
843 new_version = Version.new
843 new_version = Version.new
844 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
844 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
845 self.versions << new_version
845 self.versions << new_version
846 end
846 end
847 end
847 end
848
848
849 # Copies issue categories from +project+
849 # Copies issue categories from +project+
850 def copy_issue_categories(project)
850 def copy_issue_categories(project)
851 project.issue_categories.each do |issue_category|
851 project.issue_categories.each do |issue_category|
852 new_issue_category = IssueCategory.new
852 new_issue_category = IssueCategory.new
853 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
853 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
854 self.issue_categories << new_issue_category
854 self.issue_categories << new_issue_category
855 end
855 end
856 end
856 end
857
857
858 # Copies issues from +project+
858 # Copies issues from +project+
859 def copy_issues(project)
859 def copy_issues(project)
860 # Stores the source issue id as a key and the copied issues as the
860 # Stores the source issue id as a key and the copied issues as the
861 # value. Used to map the two together for issue relations.
861 # value. Used to map the two together for issue relations.
862 issues_map = {}
862 issues_map = {}
863
863
864 # Store status and reopen locked/closed versions
864 # Store status and reopen locked/closed versions
865 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
865 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
866 version_statuses.each do |version, status|
866 version_statuses.each do |version, status|
867 version.update_attribute :status, 'open'
867 version.update_attribute :status, 'open'
868 end
868 end
869
869
870 # Get issues sorted by root_id, lft so that parent issues
870 # Get issues sorted by root_id, lft so that parent issues
871 # get copied before their children
871 # get copied before their children
872 project.issues.reorder('root_id, lft').each do |issue|
872 project.issues.reorder('root_id, lft').each do |issue|
873 new_issue = Issue.new
873 new_issue = Issue.new
874 new_issue.copy_from(issue, :subtasks => false, :link => false)
874 new_issue.copy_from(issue, :subtasks => false, :link => false)
875 new_issue.project = self
875 new_issue.project = self
876 # Changing project resets the custom field values
876 # Changing project resets the custom field values
877 # TODO: handle this in Issue#project=
877 # TODO: handle this in Issue#project=
878 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
878 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
879 # Reassign fixed_versions by name, since names are unique per project
879 # Reassign fixed_versions by name, since names are unique per project
880 if issue.fixed_version && issue.fixed_version.project == project
880 if issue.fixed_version && issue.fixed_version.project == project
881 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
881 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
882 end
882 end
883 # Reassign the category by name, since names are unique per project
883 # Reassign the category by name, since names are unique per project
884 if issue.category
884 if issue.category
885 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
885 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
886 end
886 end
887 # Parent issue
887 # Parent issue
888 if issue.parent_id
888 if issue.parent_id
889 if copied_parent = issues_map[issue.parent_id]
889 if copied_parent = issues_map[issue.parent_id]
890 new_issue.parent_issue_id = copied_parent.id
890 new_issue.parent_issue_id = copied_parent.id
891 end
891 end
892 end
892 end
893
893
894 self.issues << new_issue
894 self.issues << new_issue
895 if new_issue.new_record?
895 if new_issue.new_record?
896 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
896 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
897 else
897 else
898 issues_map[issue.id] = new_issue unless new_issue.new_record?
898 issues_map[issue.id] = new_issue unless new_issue.new_record?
899 end
899 end
900 end
900 end
901
901
902 # Restore locked/closed version statuses
902 # Restore locked/closed version statuses
903 version_statuses.each do |version, status|
903 version_statuses.each do |version, status|
904 version.update_attribute :status, status
904 version.update_attribute :status, status
905 end
905 end
906
906
907 # Relations after in case issues related each other
907 # Relations after in case issues related each other
908 project.issues.each do |issue|
908 project.issues.each do |issue|
909 new_issue = issues_map[issue.id]
909 new_issue = issues_map[issue.id]
910 unless new_issue
910 unless new_issue
911 # Issue was not copied
911 # Issue was not copied
912 next
912 next
913 end
913 end
914
914
915 # Relations
915 # Relations
916 issue.relations_from.each do |source_relation|
916 issue.relations_from.each do |source_relation|
917 new_issue_relation = IssueRelation.new
917 new_issue_relation = IssueRelation.new
918 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
918 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
919 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
919 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
920 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
920 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
921 new_issue_relation.issue_to = source_relation.issue_to
921 new_issue_relation.issue_to = source_relation.issue_to
922 end
922 end
923 new_issue.relations_from << new_issue_relation
923 new_issue.relations_from << new_issue_relation
924 end
924 end
925
925
926 issue.relations_to.each do |source_relation|
926 issue.relations_to.each do |source_relation|
927 new_issue_relation = IssueRelation.new
927 new_issue_relation = IssueRelation.new
928 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
928 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
929 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
929 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
930 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
930 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
931 new_issue_relation.issue_from = source_relation.issue_from
931 new_issue_relation.issue_from = source_relation.issue_from
932 end
932 end
933 new_issue.relations_to << new_issue_relation
933 new_issue.relations_to << new_issue_relation
934 end
934 end
935 end
935 end
936 end
936 end
937
937
938 # Copies members from +project+
938 # Copies members from +project+
939 def copy_members(project)
939 def copy_members(project)
940 # Copy users first, then groups to handle members with inherited and given roles
940 # Copy users first, then groups to handle members with inherited and given roles
941 members_to_copy = []
941 members_to_copy = []
942 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
942 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
943 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
943 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
944
944
945 members_to_copy.each do |member|
945 members_to_copy.each do |member|
946 new_member = Member.new
946 new_member = Member.new
947 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
947 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
948 # only copy non inherited roles
948 # only copy non inherited roles
949 # inherited roles will be added when copying the group membership
949 # inherited roles will be added when copying the group membership
950 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
950 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
951 next if role_ids.empty?
951 next if role_ids.empty?
952 new_member.role_ids = role_ids
952 new_member.role_ids = role_ids
953 new_member.project = self
953 new_member.project = self
954 self.members << new_member
954 self.members << new_member
955 end
955 end
956 end
956 end
957
957
958 # Copies queries from +project+
958 # Copies queries from +project+
959 def copy_queries(project)
959 def copy_queries(project)
960 project.queries.each do |query|
960 project.queries.each do |query|
961 new_query = IssueQuery.new
961 new_query = IssueQuery.new
962 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
962 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
963 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
963 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
964 new_query.project = self
964 new_query.project = self
965 new_query.user_id = query.user_id
965 new_query.user_id = query.user_id
966 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
966 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
967 self.queries << new_query
967 self.queries << new_query
968 end
968 end
969 end
969 end
970
970
971 # Copies boards from +project+
971 # Copies boards from +project+
972 def copy_boards(project)
972 def copy_boards(project)
973 project.boards.each do |board|
973 project.boards.each do |board|
974 new_board = Board.new
974 new_board = Board.new
975 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
975 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
976 new_board.project = self
976 new_board.project = self
977 self.boards << new_board
977 self.boards << new_board
978 end
978 end
979 end
979 end
980
980
981 def allowed_permissions
981 def allowed_permissions
982 @allowed_permissions ||= begin
982 @allowed_permissions ||= begin
983 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
983 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
984 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
984 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
985 end
985 end
986 end
986 end
987
987
988 def allowed_actions
988 def allowed_actions
989 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
989 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
990 end
990 end
991
991
992 # Returns all the active Systemwide and project specific activities
992 # Returns all the active Systemwide and project specific activities
993 def active_activities
993 def active_activities
994 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
994 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
995
995
996 if overridden_activity_ids.empty?
996 if overridden_activity_ids.empty?
997 return TimeEntryActivity.shared.active
997 return TimeEntryActivity.shared.active
998 else
998 else
999 return system_activities_and_project_overrides
999 return system_activities_and_project_overrides
1000 end
1000 end
1001 end
1001 end
1002
1002
1003 # Returns all the Systemwide and project specific activities
1003 # Returns all the Systemwide and project specific activities
1004 # (inactive and active)
1004 # (inactive and active)
1005 def all_activities
1005 def all_activities
1006 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
1006 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
1007
1007
1008 if overridden_activity_ids.empty?
1008 if overridden_activity_ids.empty?
1009 return TimeEntryActivity.shared
1009 return TimeEntryActivity.shared
1010 else
1010 else
1011 return system_activities_and_project_overrides(true)
1011 return system_activities_and_project_overrides(true)
1012 end
1012 end
1013 end
1013 end
1014
1014
1015 # Returns the systemwide active activities merged with the project specific overrides
1015 # Returns the systemwide active activities merged with the project specific overrides
1016 def system_activities_and_project_overrides(include_inactive=false)
1016 def system_activities_and_project_overrides(include_inactive=false)
1017 t = TimeEntryActivity.table_name
1017 t = TimeEntryActivity.table_name
1018 scope = TimeEntryActivity.where(
1018 scope = TimeEntryActivity.where(
1019 "(#{t}.project_id IS NULL AND #{t}.id NOT IN (?)) OR (#{t}.project_id = ?)",
1019 "(#{t}.project_id IS NULL AND #{t}.id NOT IN (?)) OR (#{t}.project_id = ?)",
1020 time_entry_activities.map(&:parent_id), id
1020 time_entry_activities.map(&:parent_id), id
1021 )
1021 )
1022 unless include_inactive
1022 unless include_inactive
1023 scope = scope.active
1023 scope = scope.active
1024 end
1024 end
1025 scope
1025 scope
1026 end
1026 end
1027
1027
1028 # Archives subprojects recursively
1028 # Archives subprojects recursively
1029 def archive!
1029 def archive!
1030 children.each do |subproject|
1030 children.each do |subproject|
1031 subproject.send :archive!
1031 subproject.send :archive!
1032 end
1032 end
1033 update_attribute :status, STATUS_ARCHIVED
1033 update_attribute :status, STATUS_ARCHIVED
1034 end
1034 end
1035 end
1035 end
@@ -1,975 +1,988
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :projects, :trackers, :issue_statuses, :issues,
21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 :journals, :journal_details,
22 :journals, :journal_details,
23 :enumerations, :users, :issue_categories,
23 :enumerations, :users, :issue_categories,
24 :projects_trackers,
24 :projects_trackers,
25 :custom_fields,
25 :custom_fields,
26 :custom_fields_projects,
26 :custom_fields_projects,
27 :custom_fields_trackers,
27 :custom_fields_trackers,
28 :custom_values,
28 :custom_values,
29 :roles,
29 :roles,
30 :member_roles,
30 :member_roles,
31 :members,
31 :members,
32 :enabled_modules,
32 :enabled_modules,
33 :versions,
33 :versions,
34 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
34 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
35 :groups_users,
35 :groups_users,
36 :boards, :messages,
36 :boards, :messages,
37 :repositories,
37 :repositories,
38 :news, :comments,
38 :news, :comments,
39 :documents,
39 :documents,
40 :workflows
40 :workflows
41
41
42 def setup
42 def setup
43 @ecookbook = Project.find(1)
43 @ecookbook = Project.find(1)
44 @ecookbook_sub1 = Project.find(3)
44 @ecookbook_sub1 = Project.find(3)
45 set_tmp_attachments_directory
45 set_tmp_attachments_directory
46 User.current = nil
46 User.current = nil
47 end
47 end
48
48
49 def test_truth
49 def test_truth
50 assert_kind_of Project, @ecookbook
50 assert_kind_of Project, @ecookbook
51 assert_equal "eCookbook", @ecookbook.name
51 assert_equal "eCookbook", @ecookbook.name
52 end
52 end
53
53
54 def test_default_attributes
54 def test_default_attributes
55 with_settings :default_projects_public => '1' do
55 with_settings :default_projects_public => '1' do
56 assert_equal true, Project.new.is_public
56 assert_equal true, Project.new.is_public
57 assert_equal false, Project.new(:is_public => false).is_public
57 assert_equal false, Project.new(:is_public => false).is_public
58 end
58 end
59
59
60 with_settings :default_projects_public => '0' do
60 with_settings :default_projects_public => '0' do
61 assert_equal false, Project.new.is_public
61 assert_equal false, Project.new.is_public
62 assert_equal true, Project.new(:is_public => true).is_public
62 assert_equal true, Project.new(:is_public => true).is_public
63 end
63 end
64
64
65 with_settings :sequential_project_identifiers => '1' do
65 with_settings :sequential_project_identifiers => '1' do
66 assert !Project.new.identifier.blank?
66 assert !Project.new.identifier.blank?
67 assert Project.new(:identifier => '').identifier.blank?
67 assert Project.new(:identifier => '').identifier.blank?
68 end
68 end
69
69
70 with_settings :sequential_project_identifiers => '0' do
70 with_settings :sequential_project_identifiers => '0' do
71 assert Project.new.identifier.blank?
71 assert Project.new.identifier.blank?
72 assert !Project.new(:identifier => 'test').blank?
72 assert !Project.new(:identifier => 'test').blank?
73 end
73 end
74
74
75 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
75 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
76 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
76 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
77 end
77 end
78 end
78 end
79
79
80 def test_default_trackers_should_match_default_tracker_ids_setting
80 def test_default_trackers_should_match_default_tracker_ids_setting
81 with_settings :default_projects_tracker_ids => ['1', '3'] do
81 with_settings :default_projects_tracker_ids => ['1', '3'] do
82 assert_equal Tracker.find(1, 3).sort, Project.new.trackers.sort
82 assert_equal Tracker.find(1, 3).sort, Project.new.trackers.sort
83 end
83 end
84 end
84 end
85
85
86 def test_default_trackers_should_be_all_trackers_with_blank_setting
86 def test_default_trackers_should_be_all_trackers_with_blank_setting
87 with_settings :default_projects_tracker_ids => nil do
87 with_settings :default_projects_tracker_ids => nil do
88 assert_equal Tracker.all.sort, Project.new.trackers.sort
88 assert_equal Tracker.all.sort, Project.new.trackers.sort
89 end
89 end
90 end
90 end
91
91
92 def test_default_trackers_should_be_empty_with_empty_setting
92 def test_default_trackers_should_be_empty_with_empty_setting
93 with_settings :default_projects_tracker_ids => [] do
93 with_settings :default_projects_tracker_ids => [] do
94 assert_equal [], Project.new.trackers
94 assert_equal [], Project.new.trackers
95 end
95 end
96 end
96 end
97
97
98 def test_default_trackers_should_not_replace_initialized_trackers
98 def test_default_trackers_should_not_replace_initialized_trackers
99 with_settings :default_projects_tracker_ids => ['1', '3'] do
99 with_settings :default_projects_tracker_ids => ['1', '3'] do
100 assert_equal Tracker.find(1, 2).sort, Project.new(:tracker_ids => [1, 2]).trackers.sort
100 assert_equal Tracker.find(1, 2).sort, Project.new(:tracker_ids => [1, 2]).trackers.sort
101 end
101 end
102 end
102 end
103
103
104 def test_update
104 def test_update
105 assert_equal "eCookbook", @ecookbook.name
105 assert_equal "eCookbook", @ecookbook.name
106 @ecookbook.name = "eCook"
106 @ecookbook.name = "eCook"
107 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
107 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
108 @ecookbook.reload
108 @ecookbook.reload
109 assert_equal "eCook", @ecookbook.name
109 assert_equal "eCook", @ecookbook.name
110 end
110 end
111
111
112 def test_validate_identifier
112 def test_validate_identifier
113 to_test = {"abc" => true,
113 to_test = {"abc" => true,
114 "ab12" => true,
114 "ab12" => true,
115 "ab-12" => true,
115 "ab-12" => true,
116 "ab_12" => true,
116 "ab_12" => true,
117 "12" => false,
117 "12" => false,
118 "new" => false}
118 "new" => false}
119
119
120 to_test.each do |identifier, valid|
120 to_test.each do |identifier, valid|
121 p = Project.new
121 p = Project.new
122 p.identifier = identifier
122 p.identifier = identifier
123 p.valid?
123 p.valid?
124 if valid
124 if valid
125 assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid"
125 assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid"
126 else
126 else
127 assert p.errors['identifier'].present?, "identifier #{identifier} was valid"
127 assert p.errors['identifier'].present?, "identifier #{identifier} was valid"
128 end
128 end
129 end
129 end
130 end
130 end
131
131
132 def test_identifier_should_not_be_frozen_for_a_new_project
132 def test_identifier_should_not_be_frozen_for_a_new_project
133 assert_equal false, Project.new.identifier_frozen?
133 assert_equal false, Project.new.identifier_frozen?
134 end
134 end
135
135
136 def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier
136 def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier
137 Project.where(:id => 1).update_all(["identifier = ''"])
137 Project.where(:id => 1).update_all(["identifier = ''"])
138 assert_equal false, Project.find(1).identifier_frozen?
138 assert_equal false, Project.find(1).identifier_frozen?
139 end
139 end
140
140
141 def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier
141 def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier
142 assert_equal true, Project.find(1).identifier_frozen?
142 assert_equal true, Project.find(1).identifier_frozen?
143 end
143 end
144
144
145 def test_members_should_be_active_users
145 def test_members_should_be_active_users
146 Project.all.each do |project|
146 Project.all.each do |project|
147 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
147 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
148 end
148 end
149 end
149 end
150
150
151 def test_users_should_be_active_users
151 def test_users_should_be_active_users
152 Project.all.each do |project|
152 Project.all.each do |project|
153 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
153 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
154 end
154 end
155 end
155 end
156
156
157 def test_open_scope_on_issues_association
157 def test_open_scope_on_issues_association
158 assert_kind_of Issue, Project.find(1).issues.open.first
158 assert_kind_of Issue, Project.find(1).issues.open.first
159 end
159 end
160
160
161 def test_archive
161 def test_archive
162 user = @ecookbook.members.first.user
162 user = @ecookbook.members.first.user
163 @ecookbook.archive
163 @ecookbook.archive
164 @ecookbook.reload
164 @ecookbook.reload
165
165
166 assert !@ecookbook.active?
166 assert !@ecookbook.active?
167 assert @ecookbook.archived?
167 assert @ecookbook.archived?
168 assert !user.projects.include?(@ecookbook)
168 assert !user.projects.include?(@ecookbook)
169 # Subproject are also archived
169 # Subproject are also archived
170 assert !@ecookbook.children.empty?
170 assert !@ecookbook.children.empty?
171 assert @ecookbook.descendants.active.empty?
171 assert @ecookbook.descendants.active.empty?
172 end
172 end
173
173
174 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
174 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
175 # Assign an issue of a project to a version of a child project
175 # Assign an issue of a project to a version of a child project
176 Issue.find(4).update_attribute :fixed_version_id, 4
176 Issue.find(4).update_attribute :fixed_version_id, 4
177
177
178 assert_no_difference "Project.where(:status => Project::STATUS_ARCHIVED).count" do
178 assert_no_difference "Project.where(:status => Project::STATUS_ARCHIVED).count" do
179 assert_equal false, @ecookbook.archive
179 assert_equal false, @ecookbook.archive
180 end
180 end
181 @ecookbook.reload
181 @ecookbook.reload
182 assert @ecookbook.active?
182 assert @ecookbook.active?
183 end
183 end
184
184
185 def test_unarchive
185 def test_unarchive
186 user = @ecookbook.members.first.user
186 user = @ecookbook.members.first.user
187 @ecookbook.archive
187 @ecookbook.archive
188 # A subproject of an archived project can not be unarchived
188 # A subproject of an archived project can not be unarchived
189 assert !@ecookbook_sub1.unarchive
189 assert !@ecookbook_sub1.unarchive
190
190
191 # Unarchive project
191 # Unarchive project
192 assert @ecookbook.unarchive
192 assert @ecookbook.unarchive
193 @ecookbook.reload
193 @ecookbook.reload
194 assert @ecookbook.active?
194 assert @ecookbook.active?
195 assert !@ecookbook.archived?
195 assert !@ecookbook.archived?
196 assert user.projects.include?(@ecookbook)
196 assert user.projects.include?(@ecookbook)
197 # Subproject can now be unarchived
197 # Subproject can now be unarchived
198 @ecookbook_sub1.reload
198 @ecookbook_sub1.reload
199 assert @ecookbook_sub1.unarchive
199 assert @ecookbook_sub1.unarchive
200 end
200 end
201
201
202 def test_destroy
202 def test_destroy
203 # 2 active members
203 # 2 active members
204 assert_equal 2, @ecookbook.members.size
204 assert_equal 2, @ecookbook.members.size
205 # and 1 is locked
205 # and 1 is locked
206 assert_equal 3, Member.where(:project_id => @ecookbook.id).count
206 assert_equal 3, Member.where(:project_id => @ecookbook.id).count
207 # some boards
207 # some boards
208 assert @ecookbook.boards.any?
208 assert @ecookbook.boards.any?
209
209
210 @ecookbook.destroy
210 @ecookbook.destroy
211 # make sure that the project non longer exists
211 # make sure that the project non longer exists
212 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
212 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
213 # make sure related data was removed
213 # make sure related data was removed
214 assert_nil Member.where(:project_id => @ecookbook.id).first
214 assert_nil Member.where(:project_id => @ecookbook.id).first
215 assert_nil Board.where(:project_id => @ecookbook.id).first
215 assert_nil Board.where(:project_id => @ecookbook.id).first
216 assert_nil Issue.where(:project_id => @ecookbook.id).first
216 assert_nil Issue.where(:project_id => @ecookbook.id).first
217 end
217 end
218
218
219 def test_destroy_should_destroy_subtasks
219 def test_destroy_should_destroy_subtasks
220 issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')}
220 issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')}
221 issues[0].update_attribute :parent_issue_id, issues[1].id
221 issues[0].update_attribute :parent_issue_id, issues[1].id
222 issues[2].update_attribute :parent_issue_id, issues[1].id
222 issues[2].update_attribute :parent_issue_id, issues[1].id
223 assert_equal 2, issues[1].children.count
223 assert_equal 2, issues[1].children.count
224
224
225 assert_nothing_raised do
225 assert_nothing_raised do
226 Project.find(1).destroy
226 Project.find(1).destroy
227 end
227 end
228 assert_equal 0, Issue.where(:id => issues.map(&:id)).count
228 assert_equal 0, Issue.where(:id => issues.map(&:id)).count
229 end
229 end
230
230
231 def test_destroying_root_projects_should_clear_data
231 def test_destroying_root_projects_should_clear_data
232 Project.roots.each do |root|
232 Project.roots.each do |root|
233 root.destroy
233 root.destroy
234 end
234 end
235
235
236 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
236 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
237 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
237 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
238 assert_equal 0, MemberRole.count
238 assert_equal 0, MemberRole.count
239 assert_equal 0, Issue.count
239 assert_equal 0, Issue.count
240 assert_equal 0, Journal.count
240 assert_equal 0, Journal.count
241 assert_equal 0, JournalDetail.count
241 assert_equal 0, JournalDetail.count
242 assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}"
242 assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}"
243 assert_equal 0, EnabledModule.count
243 assert_equal 0, EnabledModule.count
244 assert_equal 0, IssueCategory.count
244 assert_equal 0, IssueCategory.count
245 assert_equal 0, IssueRelation.count
245 assert_equal 0, IssueRelation.count
246 assert_equal 0, Board.count
246 assert_equal 0, Board.count
247 assert_equal 0, Message.count
247 assert_equal 0, Message.count
248 assert_equal 0, News.count
248 assert_equal 0, News.count
249 assert_equal 0, Query.where("project_id IS NOT NULL").count
249 assert_equal 0, Query.where("project_id IS NOT NULL").count
250 assert_equal 0, Repository.count
250 assert_equal 0, Repository.count
251 assert_equal 0, Changeset.count
251 assert_equal 0, Changeset.count
252 assert_equal 0, Change.count
252 assert_equal 0, Change.count
253 assert_equal 0, Comment.count
253 assert_equal 0, Comment.count
254 assert_equal 0, TimeEntry.count
254 assert_equal 0, TimeEntry.count
255 assert_equal 0, Version.count
255 assert_equal 0, Version.count
256 assert_equal 0, Watcher.count
256 assert_equal 0, Watcher.count
257 assert_equal 0, Wiki.count
257 assert_equal 0, Wiki.count
258 assert_equal 0, WikiPage.count
258 assert_equal 0, WikiPage.count
259 assert_equal 0, WikiContent.count
259 assert_equal 0, WikiContent.count
260 assert_equal 0, WikiContent::Version.count
260 assert_equal 0, WikiContent::Version.count
261 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").count
261 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").count
262 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").count
262 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").count
263 assert_equal 0, CustomValue.where(:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']).count
263 assert_equal 0, CustomValue.where(:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']).count
264 end
264 end
265
265
266 def test_destroy_should_delete_time_entries_custom_values
266 def test_destroy_should_delete_time_entries_custom_values
267 project = Project.generate!
267 project = Project.generate!
268 time_entry = TimeEntry.generate!(:project => project, :custom_field_values => {10 => '1'})
268 time_entry = TimeEntry.generate!(:project => project, :custom_field_values => {10 => '1'})
269
269
270 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
270 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
271 assert project.destroy
271 assert project.destroy
272 end
272 end
273 end
273 end
274
274
275 def test_move_an_orphan_project_to_a_root_project
275 def test_move_an_orphan_project_to_a_root_project
276 sub = Project.find(2)
276 sub = Project.find(2)
277 sub.set_parent! @ecookbook
277 sub.set_parent! @ecookbook
278 assert_equal @ecookbook.id, sub.parent.id
278 assert_equal @ecookbook.id, sub.parent.id
279 @ecookbook.reload
279 @ecookbook.reload
280 assert_equal 4, @ecookbook.children.size
280 assert_equal 4, @ecookbook.children.size
281 end
281 end
282
282
283 def test_move_an_orphan_project_to_a_subproject
283 def test_move_an_orphan_project_to_a_subproject
284 sub = Project.find(2)
284 sub = Project.find(2)
285 assert sub.set_parent!(@ecookbook_sub1)
285 assert sub.set_parent!(@ecookbook_sub1)
286 end
286 end
287
287
288 def test_move_a_root_project_to_a_project
288 def test_move_a_root_project_to_a_project
289 sub = @ecookbook
289 sub = @ecookbook
290 assert sub.set_parent!(Project.find(2))
290 assert sub.set_parent!(Project.find(2))
291 end
291 end
292
292
293 def test_should_not_move_a_project_to_its_children
293 def test_should_not_move_a_project_to_its_children
294 sub = @ecookbook
294 sub = @ecookbook
295 assert !(sub.set_parent!(Project.find(3)))
295 assert !(sub.set_parent!(Project.find(3)))
296 end
296 end
297
297
298 def test_set_parent_should_add_roots_in_alphabetical_order
298 def test_set_parent_should_add_roots_in_alphabetical_order
299 ProjectCustomField.delete_all
299 ProjectCustomField.delete_all
300 Project.delete_all
300 Project.delete_all
301 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
301 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
302 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
302 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
303 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
303 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
304 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
304 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
305
305
306 assert_equal 4, Project.count
306 assert_equal 4, Project.count
307 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
307 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
308 end
308 end
309
309
310 def test_set_parent_should_add_children_in_alphabetical_order
310 def test_set_parent_should_add_children_in_alphabetical_order
311 ProjectCustomField.delete_all
311 ProjectCustomField.delete_all
312 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
312 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
313 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
313 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
314 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
314 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
315 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
315 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
316 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
316 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
317
317
318 parent.reload
318 parent.reload
319 assert_equal 4, parent.children.size
319 assert_equal 4, parent.children.size
320 assert_equal parent.children.sort_by(&:name), parent.children.to_a
320 assert_equal parent.children.sort_by(&:name), parent.children.to_a
321 end
321 end
322
322
323 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
323 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
324 # Parent issue with a hierarchy project's fixed version
324 # Parent issue with a hierarchy project's fixed version
325 parent_issue = Issue.find(1)
325 parent_issue = Issue.find(1)
326 parent_issue.update_attribute(:fixed_version_id, 4)
326 parent_issue.update_attribute(:fixed_version_id, 4)
327 parent_issue.reload
327 parent_issue.reload
328 assert_equal 4, parent_issue.fixed_version_id
328 assert_equal 4, parent_issue.fixed_version_id
329
329
330 # Should keep fixed versions for the issues
330 # Should keep fixed versions for the issues
331 issue_with_local_fixed_version = Issue.find(5)
331 issue_with_local_fixed_version = Issue.find(5)
332 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
332 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
333 issue_with_local_fixed_version.reload
333 issue_with_local_fixed_version.reload
334 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
334 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
335
335
336 # Local issue with hierarchy fixed_version
336 # Local issue with hierarchy fixed_version
337 issue_with_hierarchy_fixed_version = Issue.find(13)
337 issue_with_hierarchy_fixed_version = Issue.find(13)
338 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
338 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
339 issue_with_hierarchy_fixed_version.reload
339 issue_with_hierarchy_fixed_version.reload
340 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
340 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
341
341
342 # Move project out of the issue's hierarchy
342 # Move project out of the issue's hierarchy
343 moved_project = Project.find(3)
343 moved_project = Project.find(3)
344 moved_project.set_parent!(Project.find(2))
344 moved_project.set_parent!(Project.find(2))
345 parent_issue.reload
345 parent_issue.reload
346 issue_with_local_fixed_version.reload
346 issue_with_local_fixed_version.reload
347 issue_with_hierarchy_fixed_version.reload
347 issue_with_hierarchy_fixed_version.reload
348
348
349 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
349 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
350 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
350 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
351 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
351 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
352 end
352 end
353
353
354 def test_parent
354 def test_parent
355 p = Project.find(6).parent
355 p = Project.find(6).parent
356 assert p.is_a?(Project)
356 assert p.is_a?(Project)
357 assert_equal 5, p.id
357 assert_equal 5, p.id
358 end
358 end
359
359
360 def test_ancestors
360 def test_ancestors
361 a = Project.find(6).ancestors
361 a = Project.find(6).ancestors
362 assert a.first.is_a?(Project)
362 assert a.first.is_a?(Project)
363 assert_equal [1, 5], a.collect(&:id)
363 assert_equal [1, 5], a.collect(&:id)
364 end
364 end
365
365
366 def test_root
366 def test_root
367 r = Project.find(6).root
367 r = Project.find(6).root
368 assert r.is_a?(Project)
368 assert r.is_a?(Project)
369 assert_equal 1, r.id
369 assert_equal 1, r.id
370 end
370 end
371
371
372 def test_children
372 def test_children
373 c = Project.find(1).children
373 c = Project.find(1).children
374 assert c.first.is_a?(Project)
374 assert c.first.is_a?(Project)
375 assert_equal [5, 3, 4], c.collect(&:id)
375 assert_equal [5, 3, 4], c.collect(&:id)
376 end
376 end
377
377
378 def test_descendants
378 def test_descendants
379 d = Project.find(1).descendants
379 d = Project.find(1).descendants
380 assert d.first.is_a?(Project)
380 assert d.first.is_a?(Project)
381 assert_equal [5, 6, 3, 4], d.collect(&:id)
381 assert_equal [5, 6, 3, 4], d.collect(&:id)
382 end
382 end
383
383
384 def test_allowed_parents_should_be_empty_for_non_member_user
384 def test_allowed_parents_should_be_empty_for_non_member_user
385 Role.non_member.add_permission!(:add_project)
385 Role.non_member.add_permission!(:add_project)
386 user = User.find(9)
386 user = User.find(9)
387 assert user.memberships.empty?
387 assert user.memberships.empty?
388 User.current = user
388 User.current = user
389 assert Project.new.allowed_parents.compact.empty?
389 assert Project.new.allowed_parents.compact.empty?
390 end
390 end
391
391
392 def test_allowed_parents_with_add_subprojects_permission
392 def test_allowed_parents_with_add_subprojects_permission
393 Role.find(1).remove_permission!(:add_project)
393 Role.find(1).remove_permission!(:add_project)
394 Role.find(1).add_permission!(:add_subprojects)
394 Role.find(1).add_permission!(:add_subprojects)
395 User.current = User.find(2)
395 User.current = User.find(2)
396 # new project
396 # new project
397 assert !Project.new.allowed_parents.include?(nil)
397 assert !Project.new.allowed_parents.include?(nil)
398 assert Project.new.allowed_parents.include?(Project.find(1))
398 assert Project.new.allowed_parents.include?(Project.find(1))
399 # existing root project
399 # existing root project
400 assert Project.find(1).allowed_parents.include?(nil)
400 assert Project.find(1).allowed_parents.include?(nil)
401 # existing child
401 # existing child
402 assert Project.find(3).allowed_parents.include?(Project.find(1))
402 assert Project.find(3).allowed_parents.include?(Project.find(1))
403 assert !Project.find(3).allowed_parents.include?(nil)
403 assert !Project.find(3).allowed_parents.include?(nil)
404 end
404 end
405
405
406 def test_allowed_parents_with_add_project_permission
406 def test_allowed_parents_with_add_project_permission
407 Role.find(1).add_permission!(:add_project)
407 Role.find(1).add_permission!(:add_project)
408 Role.find(1).remove_permission!(:add_subprojects)
408 Role.find(1).remove_permission!(:add_subprojects)
409 User.current = User.find(2)
409 User.current = User.find(2)
410 # new project
410 # new project
411 assert Project.new.allowed_parents.include?(nil)
411 assert Project.new.allowed_parents.include?(nil)
412 assert !Project.new.allowed_parents.include?(Project.find(1))
412 assert !Project.new.allowed_parents.include?(Project.find(1))
413 # existing root project
413 # existing root project
414 assert Project.find(1).allowed_parents.include?(nil)
414 assert Project.find(1).allowed_parents.include?(nil)
415 # existing child
415 # existing child
416 assert Project.find(3).allowed_parents.include?(Project.find(1))
416 assert Project.find(3).allowed_parents.include?(Project.find(1))
417 assert Project.find(3).allowed_parents.include?(nil)
417 assert Project.find(3).allowed_parents.include?(nil)
418 end
418 end
419
419
420 def test_allowed_parents_with_add_project_and_subprojects_permission
420 def test_allowed_parents_with_add_project_and_subprojects_permission
421 Role.find(1).add_permission!(:add_project)
421 Role.find(1).add_permission!(:add_project)
422 Role.find(1).add_permission!(:add_subprojects)
422 Role.find(1).add_permission!(:add_subprojects)
423 User.current = User.find(2)
423 User.current = User.find(2)
424 # new project
424 # new project
425 assert Project.new.allowed_parents.include?(nil)
425 assert Project.new.allowed_parents.include?(nil)
426 assert Project.new.allowed_parents.include?(Project.find(1))
426 assert Project.new.allowed_parents.include?(Project.find(1))
427 # existing root project
427 # existing root project
428 assert Project.find(1).allowed_parents.include?(nil)
428 assert Project.find(1).allowed_parents.include?(nil)
429 # existing child
429 # existing child
430 assert Project.find(3).allowed_parents.include?(Project.find(1))
430 assert Project.find(3).allowed_parents.include?(Project.find(1))
431 assert Project.find(3).allowed_parents.include?(nil)
431 assert Project.find(3).allowed_parents.include?(nil)
432 end
432 end
433
433
434 def test_users_by_role
434 def test_users_by_role
435 users_by_role = Project.find(1).users_by_role
435 users_by_role = Project.find(1).users_by_role
436 assert_kind_of Hash, users_by_role
436 assert_kind_of Hash, users_by_role
437 role = Role.find(1)
437 role = Role.find(1)
438 assert_kind_of Array, users_by_role[role]
438 assert_kind_of Array, users_by_role[role]
439 assert users_by_role[role].include?(User.find(2))
439 assert users_by_role[role].include?(User.find(2))
440 end
440 end
441
441
442 def test_rolled_up_trackers
442 def test_rolled_up_trackers
443 parent = Project.find(1)
443 parent = Project.find(1)
444 parent.trackers = Tracker.find([1,2])
444 parent.trackers = Tracker.find([1,2])
445 child = parent.children.find(3)
445 child = parent.children.find(3)
446
446
447 assert_equal [1, 2], parent.tracker_ids
447 assert_equal [1, 2], parent.tracker_ids
448 assert_equal [2, 3], child.trackers.collect(&:id)
448 assert_equal [2, 3], child.trackers.collect(&:id)
449
449
450 assert_kind_of Tracker, parent.rolled_up_trackers.first
450 assert_kind_of Tracker, parent.rolled_up_trackers.first
451 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
451 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
452
452
453 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
453 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
454 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
454 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
455 end
455 end
456
456
457 def test_rolled_up_trackers_should_ignore_archived_subprojects
457 def test_rolled_up_trackers_should_ignore_archived_subprojects
458 parent = Project.find(1)
458 parent = Project.find(1)
459 parent.trackers = Tracker.find([1,2])
459 parent.trackers = Tracker.find([1,2])
460 child = parent.children.find(3)
460 child = parent.children.find(3)
461 child.trackers = Tracker.find([1,3])
461 child.trackers = Tracker.find([1,3])
462 parent.children.each(&:archive)
462 parent.children.each(&:archive)
463
463
464 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
464 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
465 end
465 end
466
466
467 test "#rolled_up_trackers should ignore projects with issue_tracking module disabled" do
467 test "#rolled_up_trackers should ignore projects with issue_tracking module disabled" do
468 parent = Project.generate!
468 parent = Project.generate!
469 parent.trackers = Tracker.find([1, 2])
469 parent.trackers = Tracker.find([1, 2])
470 child = Project.generate_with_parent!(parent)
470 child = Project.generate_with_parent!(parent)
471 child.trackers = Tracker.find([2, 3])
471 child.trackers = Tracker.find([2, 3])
472
472
473 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort
473 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort
474
474
475 assert child.disable_module!(:issue_tracking)
475 assert child.disable_module!(:issue_tracking)
476 parent.reload
476 parent.reload
477 assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort
477 assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort
478 end
478 end
479
479
480 test "#rolled_up_versions should include the versions for the current project" do
480 test "#rolled_up_versions should include the versions for the current project" do
481 project = Project.generate!
481 project = Project.generate!
482 parent_version_1 = Version.generate!(:project => project)
482 parent_version_1 = Version.generate!(:project => project)
483 parent_version_2 = Version.generate!(:project => project)
483 parent_version_2 = Version.generate!(:project => project)
484 assert_equal [parent_version_1, parent_version_2].sort,
484 assert_equal [parent_version_1, parent_version_2].sort,
485 project.rolled_up_versions.sort
485 project.rolled_up_versions.sort
486 end
486 end
487
487
488 test "#rolled_up_versions should include versions for a subproject" do
488 test "#rolled_up_versions should include versions for a subproject" do
489 project = Project.generate!
489 project = Project.generate!
490 parent_version_1 = Version.generate!(:project => project)
490 parent_version_1 = Version.generate!(:project => project)
491 parent_version_2 = Version.generate!(:project => project)
491 parent_version_2 = Version.generate!(:project => project)
492 subproject = Project.generate_with_parent!(project)
492 subproject = Project.generate_with_parent!(project)
493 subproject_version = Version.generate!(:project => subproject)
493 subproject_version = Version.generate!(:project => subproject)
494
494
495 assert_equal [parent_version_1, parent_version_2, subproject_version].sort,
495 assert_equal [parent_version_1, parent_version_2, subproject_version].sort,
496 project.rolled_up_versions.sort
496 project.rolled_up_versions.sort
497 end
497 end
498
498
499 test "#rolled_up_versions should include versions for a sub-subproject" do
499 test "#rolled_up_versions should include versions for a sub-subproject" do
500 project = Project.generate!
500 project = Project.generate!
501 parent_version_1 = Version.generate!(:project => project)
501 parent_version_1 = Version.generate!(:project => project)
502 parent_version_2 = Version.generate!(:project => project)
502 parent_version_2 = Version.generate!(:project => project)
503 subproject = Project.generate_with_parent!(project)
503 subproject = Project.generate_with_parent!(project)
504 sub_subproject = Project.generate_with_parent!(subproject)
504 sub_subproject = Project.generate_with_parent!(subproject)
505 sub_subproject_version = Version.generate!(:project => sub_subproject)
505 sub_subproject_version = Version.generate!(:project => sub_subproject)
506 project.reload
506 project.reload
507
507
508 assert_equal [parent_version_1, parent_version_2, sub_subproject_version].sort,
508 assert_equal [parent_version_1, parent_version_2, sub_subproject_version].sort,
509 project.rolled_up_versions.sort
509 project.rolled_up_versions.sort
510 end
510 end
511
511
512 test "#rolled_up_versions should only check active projects" do
512 test "#rolled_up_versions should only check active projects" do
513 project = Project.generate!
513 project = Project.generate!
514 parent_version_1 = Version.generate!(:project => project)
514 parent_version_1 = Version.generate!(:project => project)
515 parent_version_2 = Version.generate!(:project => project)
515 parent_version_2 = Version.generate!(:project => project)
516 subproject = Project.generate_with_parent!(project)
516 subproject = Project.generate_with_parent!(project)
517 subproject_version = Version.generate!(:project => subproject)
517 subproject_version = Version.generate!(:project => subproject)
518 assert subproject.archive
518 assert subproject.archive
519 project.reload
519 project.reload
520
520
521 assert !subproject.active?
521 assert !subproject.active?
522 assert_equal [parent_version_1, parent_version_2].sort,
522 assert_equal [parent_version_1, parent_version_2].sort,
523 project.rolled_up_versions.sort
523 project.rolled_up_versions.sort
524 end
524 end
525
525
526 def test_shared_versions_none_sharing
526 def test_shared_versions_none_sharing
527 p = Project.find(5)
527 p = Project.find(5)
528 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
528 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
529 assert p.shared_versions.include?(v)
529 assert p.shared_versions.include?(v)
530 assert !p.children.first.shared_versions.include?(v)
530 assert !p.children.first.shared_versions.include?(v)
531 assert !p.root.shared_versions.include?(v)
531 assert !p.root.shared_versions.include?(v)
532 assert !p.siblings.first.shared_versions.include?(v)
532 assert !p.siblings.first.shared_versions.include?(v)
533 assert !p.root.siblings.first.shared_versions.include?(v)
533 assert !p.root.siblings.first.shared_versions.include?(v)
534 end
534 end
535
535
536 def test_shared_versions_descendants_sharing
536 def test_shared_versions_descendants_sharing
537 p = Project.find(5)
537 p = Project.find(5)
538 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
538 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
539 assert p.shared_versions.include?(v)
539 assert p.shared_versions.include?(v)
540 assert p.children.first.shared_versions.include?(v)
540 assert p.children.first.shared_versions.include?(v)
541 assert !p.root.shared_versions.include?(v)
541 assert !p.root.shared_versions.include?(v)
542 assert !p.siblings.first.shared_versions.include?(v)
542 assert !p.siblings.first.shared_versions.include?(v)
543 assert !p.root.siblings.first.shared_versions.include?(v)
543 assert !p.root.siblings.first.shared_versions.include?(v)
544 end
544 end
545
545
546 def test_shared_versions_hierarchy_sharing
546 def test_shared_versions_hierarchy_sharing
547 p = Project.find(5)
547 p = Project.find(5)
548 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
548 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
549 assert p.shared_versions.include?(v)
549 assert p.shared_versions.include?(v)
550 assert p.children.first.shared_versions.include?(v)
550 assert p.children.first.shared_versions.include?(v)
551 assert p.root.shared_versions.include?(v)
551 assert p.root.shared_versions.include?(v)
552 assert !p.siblings.first.shared_versions.include?(v)
552 assert !p.siblings.first.shared_versions.include?(v)
553 assert !p.root.siblings.first.shared_versions.include?(v)
553 assert !p.root.siblings.first.shared_versions.include?(v)
554 end
554 end
555
555
556 def test_shared_versions_tree_sharing
556 def test_shared_versions_tree_sharing
557 p = Project.find(5)
557 p = Project.find(5)
558 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
558 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
559 assert p.shared_versions.include?(v)
559 assert p.shared_versions.include?(v)
560 assert p.children.first.shared_versions.include?(v)
560 assert p.children.first.shared_versions.include?(v)
561 assert p.root.shared_versions.include?(v)
561 assert p.root.shared_versions.include?(v)
562 assert p.siblings.first.shared_versions.include?(v)
562 assert p.siblings.first.shared_versions.include?(v)
563 assert !p.root.siblings.first.shared_versions.include?(v)
563 assert !p.root.siblings.first.shared_versions.include?(v)
564 end
564 end
565
565
566 def test_shared_versions_system_sharing
566 def test_shared_versions_system_sharing
567 p = Project.find(5)
567 p = Project.find(5)
568 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
568 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
569 assert p.shared_versions.include?(v)
569 assert p.shared_versions.include?(v)
570 assert p.children.first.shared_versions.include?(v)
570 assert p.children.first.shared_versions.include?(v)
571 assert p.root.shared_versions.include?(v)
571 assert p.root.shared_versions.include?(v)
572 assert p.siblings.first.shared_versions.include?(v)
572 assert p.siblings.first.shared_versions.include?(v)
573 assert p.root.siblings.first.shared_versions.include?(v)
573 assert p.root.siblings.first.shared_versions.include?(v)
574 end
574 end
575
575
576 def test_shared_versions
576 def test_shared_versions
577 parent = Project.find(1)
577 parent = Project.find(1)
578 child = parent.children.find(3)
578 child = parent.children.find(3)
579 private_child = parent.children.find(5)
579 private_child = parent.children.find(5)
580
580
581 assert_equal [1,2,3], parent.version_ids.sort
581 assert_equal [1,2,3], parent.version_ids.sort
582 assert_equal [4], child.version_ids
582 assert_equal [4], child.version_ids
583 assert_equal [6], private_child.version_ids
583 assert_equal [6], private_child.version_ids
584 assert_equal [7], Version.where(:sharing => 'system').collect(&:id)
584 assert_equal [7], Version.where(:sharing => 'system').collect(&:id)
585
585
586 assert_equal 6, parent.shared_versions.size
586 assert_equal 6, parent.shared_versions.size
587 parent.shared_versions.each do |version|
587 parent.shared_versions.each do |version|
588 assert_kind_of Version, version
588 assert_kind_of Version, version
589 end
589 end
590
590
591 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
591 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
592 end
592 end
593
593
594 def test_shared_versions_should_ignore_archived_subprojects
594 def test_shared_versions_should_ignore_archived_subprojects
595 parent = Project.find(1)
595 parent = Project.find(1)
596 child = parent.children.find(3)
596 child = parent.children.find(3)
597 child.archive
597 child.archive
598 parent.reload
598 parent.reload
599
599
600 assert_equal [1,2,3], parent.version_ids.sort
600 assert_equal [1,2,3], parent.version_ids.sort
601 assert_equal [4], child.version_ids
601 assert_equal [4], child.version_ids
602 assert !parent.shared_versions.collect(&:id).include?(4)
602 assert !parent.shared_versions.collect(&:id).include?(4)
603 end
603 end
604
604
605 def test_shared_versions_visible_to_user
605 def test_shared_versions_visible_to_user
606 user = User.find(3)
606 user = User.find(3)
607 parent = Project.find(1)
607 parent = Project.find(1)
608 child = parent.children.find(5)
608 child = parent.children.find(5)
609
609
610 assert_equal [1,2,3], parent.version_ids.sort
610 assert_equal [1,2,3], parent.version_ids.sort
611 assert_equal [6], child.version_ids
611 assert_equal [6], child.version_ids
612
612
613 versions = parent.shared_versions.visible(user)
613 versions = parent.shared_versions.visible(user)
614
614
615 assert_equal 4, versions.size
615 assert_equal 4, versions.size
616 versions.each do |version|
616 versions.each do |version|
617 assert_kind_of Version, version
617 assert_kind_of Version, version
618 end
618 end
619
619
620 assert !versions.collect(&:id).include?(6)
620 assert !versions.collect(&:id).include?(6)
621 end
621 end
622
622
623 def test_shared_versions_for_new_project_should_include_system_shared_versions
623 def test_shared_versions_for_new_project_should_include_system_shared_versions
624 p = Project.find(5)
624 p = Project.find(5)
625 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
625 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
626
626
627 assert_include v, Project.new.shared_versions
627 assert_include v, Project.new.shared_versions
628 end
628 end
629
629
630 def test_next_identifier
630 def test_next_identifier
631 ProjectCustomField.delete_all
631 ProjectCustomField.delete_all
632 Project.create!(:name => 'last', :identifier => 'p2008040')
632 Project.create!(:name => 'last', :identifier => 'p2008040')
633 assert_equal 'p2008041', Project.next_identifier
633 assert_equal 'p2008041', Project.next_identifier
634 end
634 end
635
635
636 def test_next_identifier_first_project
636 def test_next_identifier_first_project
637 Project.delete_all
637 Project.delete_all
638 assert_nil Project.next_identifier
638 assert_nil Project.next_identifier
639 end
639 end
640
640
641 def test_enabled_module_names
641 def test_enabled_module_names
642 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
642 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
643 project = Project.new
643 project = Project.new
644
644
645 project.enabled_module_names = %w(issue_tracking news)
645 project.enabled_module_names = %w(issue_tracking news)
646 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
646 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
647 end
647 end
648 end
648 end
649
649
650 def test_enabled_modules_names_with_nil_should_clear_modules
650 def test_enabled_modules_names_with_nil_should_clear_modules
651 p = Project.find(1)
651 p = Project.find(1)
652 p.enabled_module_names = nil
652 p.enabled_module_names = nil
653 assert_equal [], p.enabled_modules
653 assert_equal [], p.enabled_modules
654 end
654 end
655
655
656 test "enabled_modules should define module by names and preserve ids" do
656 test "enabled_modules should define module by names and preserve ids" do
657 @project = Project.find(1)
657 @project = Project.find(1)
658 # Remove one module
658 # Remove one module
659 modules = @project.enabled_modules.slice(0..-2)
659 modules = @project.enabled_modules.slice(0..-2)
660 assert modules.any?
660 assert modules.any?
661 assert_difference 'EnabledModule.count', -1 do
661 assert_difference 'EnabledModule.count', -1 do
662 @project.enabled_module_names = modules.collect(&:name)
662 @project.enabled_module_names = modules.collect(&:name)
663 end
663 end
664 @project.reload
664 @project.reload
665 # Ids should be preserved
665 # Ids should be preserved
666 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
666 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
667 end
667 end
668
668
669 test "enabled_modules should enable a module" do
669 test "enabled_modules should enable a module" do
670 @project = Project.find(1)
670 @project = Project.find(1)
671 @project.enabled_module_names = []
671 @project.enabled_module_names = []
672 @project.reload
672 @project.reload
673 assert_equal [], @project.enabled_module_names
673 assert_equal [], @project.enabled_module_names
674 #with string
674 #with string
675 @project.enable_module!("issue_tracking")
675 @project.enable_module!("issue_tracking")
676 assert_equal ["issue_tracking"], @project.enabled_module_names
676 assert_equal ["issue_tracking"], @project.enabled_module_names
677 #with symbol
677 #with symbol
678 @project.enable_module!(:gantt)
678 @project.enable_module!(:gantt)
679 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
679 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
680 #don't add a module twice
680 #don't add a module twice
681 @project.enable_module!("issue_tracking")
681 @project.enable_module!("issue_tracking")
682 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
682 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
683 end
683 end
684
684
685 test "enabled_modules should disable a module" do
685 test "enabled_modules should disable a module" do
686 @project = Project.find(1)
686 @project = Project.find(1)
687 #with string
687 #with string
688 assert @project.enabled_module_names.include?("issue_tracking")
688 assert @project.enabled_module_names.include?("issue_tracking")
689 @project.disable_module!("issue_tracking")
689 @project.disable_module!("issue_tracking")
690 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
690 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
691 #with symbol
691 #with symbol
692 assert @project.enabled_module_names.include?("gantt")
692 assert @project.enabled_module_names.include?("gantt")
693 @project.disable_module!(:gantt)
693 @project.disable_module!(:gantt)
694 assert ! @project.reload.enabled_module_names.include?("gantt")
694 assert ! @project.reload.enabled_module_names.include?("gantt")
695 #with EnabledModule object
695 #with EnabledModule object
696 first_module = @project.enabled_modules.first
696 first_module = @project.enabled_modules.first
697 @project.disable_module!(first_module)
697 @project.disable_module!(first_module)
698 assert ! @project.reload.enabled_module_names.include?(first_module.name)
698 assert ! @project.reload.enabled_module_names.include?(first_module.name)
699 end
699 end
700
700
701 def test_enabled_module_names_should_not_recreate_enabled_modules
701 def test_enabled_module_names_should_not_recreate_enabled_modules
702 project = Project.find(1)
702 project = Project.find(1)
703 # Remove one module
703 # Remove one module
704 modules = project.enabled_modules.slice(0..-2)
704 modules = project.enabled_modules.slice(0..-2)
705 assert modules.any?
705 assert modules.any?
706 assert_difference 'EnabledModule.count', -1 do
706 assert_difference 'EnabledModule.count', -1 do
707 project.enabled_module_names = modules.collect(&:name)
707 project.enabled_module_names = modules.collect(&:name)
708 end
708 end
709 project.reload
709 project.reload
710 # Ids should be preserved
710 # Ids should be preserved
711 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
711 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
712 end
712 end
713
713
714 def test_copy_from_existing_project
714 def test_copy_from_existing_project
715 source_project = Project.find(1)
715 source_project = Project.find(1)
716 copied_project = Project.copy_from(1)
716 copied_project = Project.copy_from(1)
717
717
718 assert copied_project
718 assert copied_project
719 # Cleared attributes
719 # Cleared attributes
720 assert copied_project.id.blank?
720 assert copied_project.id.blank?
721 assert copied_project.name.blank?
721 assert copied_project.name.blank?
722 assert copied_project.identifier.blank?
722 assert copied_project.identifier.blank?
723
723
724 # Duplicated attributes
724 # Duplicated attributes
725 assert_equal source_project.description, copied_project.description
725 assert_equal source_project.description, copied_project.description
726 assert_equal source_project.enabled_modules, copied_project.enabled_modules
727 assert_equal source_project.trackers, copied_project.trackers
726 assert_equal source_project.trackers, copied_project.trackers
728
727
729 # Default attributes
728 # Default attributes
730 assert_equal 1, copied_project.status
729 assert_equal 1, copied_project.status
731 end
730 end
732
731
732 def test_copy_from_should_copy_enabled_modules
733 source = Project.generate!
734 source.enabled_module_names = %w(issue_tracking wiki)
735
736 copy = Project.copy_from(source)
737 copy.name = 'Copy'
738 copy.identifier = 'copy'
739 assert_difference 'EnabledModule.count', 2 do
740 copy.save!
741 end
742 assert_equal 2, copy.reload.enabled_modules.count
743 assert_equal 2, source.reload.enabled_modules.count
744 end
745
733 def test_activities_should_use_the_system_activities
746 def test_activities_should_use_the_system_activities
734 project = Project.find(1)
747 project = Project.find(1)
735 assert_equal project.activities.to_a, TimeEntryActivity.where(:active => true).to_a
748 assert_equal project.activities.to_a, TimeEntryActivity.where(:active => true).to_a
736 assert_kind_of ActiveRecord::Relation, project.activities
749 assert_kind_of ActiveRecord::Relation, project.activities
737 end
750 end
738
751
739
752
740 def test_activities_should_use_the_project_specific_activities
753 def test_activities_should_use_the_project_specific_activities
741 project = Project.find(1)
754 project = Project.find(1)
742 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
755 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
743 assert overridden_activity.save!
756 assert overridden_activity.save!
744
757
745 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
758 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
746 assert_kind_of ActiveRecord::Relation, project.activities
759 assert_kind_of ActiveRecord::Relation, project.activities
747 end
760 end
748
761
749 def test_activities_should_not_include_the_inactive_project_specific_activities
762 def test_activities_should_not_include_the_inactive_project_specific_activities
750 project = Project.find(1)
763 project = Project.find(1)
751 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
764 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
752 assert overridden_activity.save!
765 assert overridden_activity.save!
753
766
754 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
767 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
755 end
768 end
756
769
757 def test_activities_should_not_include_project_specific_activities_from_other_projects
770 def test_activities_should_not_include_project_specific_activities_from_other_projects
758 project = Project.find(1)
771 project = Project.find(1)
759 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
772 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
760 assert overridden_activity.save!
773 assert overridden_activity.save!
761
774
762 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
775 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
763 end
776 end
764
777
765 def test_activities_should_handle_nils
778 def test_activities_should_handle_nils
766 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first})
779 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first})
767 TimeEntryActivity.delete_all
780 TimeEntryActivity.delete_all
768
781
769 # No activities
782 # No activities
770 project = Project.find(1)
783 project = Project.find(1)
771 assert project.activities.empty?
784 assert project.activities.empty?
772
785
773 # No system, one overridden
786 # No system, one overridden
774 assert overridden_activity.save!
787 assert overridden_activity.save!
775 project.reload
788 project.reload
776 assert_equal [overridden_activity], project.activities
789 assert_equal [overridden_activity], project.activities
777 end
790 end
778
791
779 def test_activities_should_override_system_activities_with_project_activities
792 def test_activities_should_override_system_activities_with_project_activities
780 project = Project.find(1)
793 project = Project.find(1)
781 parent_activity = TimeEntryActivity.first
794 parent_activity = TimeEntryActivity.first
782 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
795 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
783 assert overridden_activity.save!
796 assert overridden_activity.save!
784
797
785 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
798 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
786 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
799 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
787 end
800 end
788
801
789 def test_activities_should_include_inactive_activities_if_specified
802 def test_activities_should_include_inactive_activities_if_specified
790 project = Project.find(1)
803 project = Project.find(1)
791 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
804 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
792 assert overridden_activity.save!
805 assert overridden_activity.save!
793
806
794 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
807 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
795 end
808 end
796
809
797 test 'activities should not include active System activities if the project has an override that is inactive' do
810 test 'activities should not include active System activities if the project has an override that is inactive' do
798 project = Project.find(1)
811 project = Project.find(1)
799 system_activity = TimeEntryActivity.find_by_name('Design')
812 system_activity = TimeEntryActivity.find_by_name('Design')
800 assert system_activity.active?
813 assert system_activity.active?
801 overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false)
814 overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false)
802 assert overridden_activity.save!
815 assert overridden_activity.save!
803
816
804 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
817 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
805 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
818 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
806 end
819 end
807
820
808 def test_close_completed_versions
821 def test_close_completed_versions
809 Version.update_all("status = 'open'")
822 Version.update_all("status = 'open'")
810 project = Project.find(1)
823 project = Project.find(1)
811 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
824 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
812 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
825 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
813 project.close_completed_versions
826 project.close_completed_versions
814 project.reload
827 project.reload
815 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
828 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
816 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
829 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
817 end
830 end
818
831
819 test "#start_date should be nil if there are no issues on the project" do
832 test "#start_date should be nil if there are no issues on the project" do
820 project = Project.generate!
833 project = Project.generate!
821 assert_nil project.start_date
834 assert_nil project.start_date
822 end
835 end
823
836
824 test "#start_date should be nil when issues have no start date" do
837 test "#start_date should be nil when issues have no start date" do
825 project = Project.generate!
838 project = Project.generate!
826 project.trackers << Tracker.generate!
839 project.trackers << Tracker.generate!
827 early = 7.days.ago.to_date
840 early = 7.days.ago.to_date
828 Issue.generate!(:project => project, :start_date => nil)
841 Issue.generate!(:project => project, :start_date => nil)
829
842
830 assert_nil project.start_date
843 assert_nil project.start_date
831 end
844 end
832
845
833 test "#start_date should be the earliest start date of it's issues" do
846 test "#start_date should be the earliest start date of it's issues" do
834 project = Project.generate!
847 project = Project.generate!
835 project.trackers << Tracker.generate!
848 project.trackers << Tracker.generate!
836 early = 7.days.ago.to_date
849 early = 7.days.ago.to_date
837 Issue.generate!(:project => project, :start_date => Date.today)
850 Issue.generate!(:project => project, :start_date => Date.today)
838 Issue.generate!(:project => project, :start_date => early)
851 Issue.generate!(:project => project, :start_date => early)
839
852
840 assert_equal early, project.start_date
853 assert_equal early, project.start_date
841 end
854 end
842
855
843 test "#due_date should be nil if there are no issues on the project" do
856 test "#due_date should be nil if there are no issues on the project" do
844 project = Project.generate!
857 project = Project.generate!
845 assert_nil project.due_date
858 assert_nil project.due_date
846 end
859 end
847
860
848 test "#due_date should be nil if there are no issues with due dates" do
861 test "#due_date should be nil if there are no issues with due dates" do
849 project = Project.generate!
862 project = Project.generate!
850 project.trackers << Tracker.generate!
863 project.trackers << Tracker.generate!
851 Issue.generate!(:project => project, :due_date => nil)
864 Issue.generate!(:project => project, :due_date => nil)
852
865
853 assert_nil project.due_date
866 assert_nil project.due_date
854 end
867 end
855
868
856 test "#due_date should be the latest due date of it's issues" do
869 test "#due_date should be the latest due date of it's issues" do
857 project = Project.generate!
870 project = Project.generate!
858 project.trackers << Tracker.generate!
871 project.trackers << Tracker.generate!
859 future = 7.days.from_now.to_date
872 future = 7.days.from_now.to_date
860 Issue.generate!(:project => project, :due_date => future)
873 Issue.generate!(:project => project, :due_date => future)
861 Issue.generate!(:project => project, :due_date => Date.today)
874 Issue.generate!(:project => project, :due_date => Date.today)
862
875
863 assert_equal future, project.due_date
876 assert_equal future, project.due_date
864 end
877 end
865
878
866 test "#due_date should be the latest due date of it's versions" do
879 test "#due_date should be the latest due date of it's versions" do
867 project = Project.generate!
880 project = Project.generate!
868 future = 7.days.from_now.to_date
881 future = 7.days.from_now.to_date
869 project.versions << Version.generate!(:effective_date => future)
882 project.versions << Version.generate!(:effective_date => future)
870 project.versions << Version.generate!(:effective_date => Date.today)
883 project.versions << Version.generate!(:effective_date => Date.today)
871
884
872 assert_equal future, project.due_date
885 assert_equal future, project.due_date
873 end
886 end
874
887
875 test "#due_date should pick the latest date from it's issues and versions" do
888 test "#due_date should pick the latest date from it's issues and versions" do
876 project = Project.generate!
889 project = Project.generate!
877 project.trackers << Tracker.generate!
890 project.trackers << Tracker.generate!
878 future = 7.days.from_now.to_date
891 future = 7.days.from_now.to_date
879 far_future = 14.days.from_now.to_date
892 far_future = 14.days.from_now.to_date
880 Issue.generate!(:project => project, :due_date => far_future)
893 Issue.generate!(:project => project, :due_date => far_future)
881 project.versions << Version.generate!(:effective_date => future)
894 project.versions << Version.generate!(:effective_date => future)
882
895
883 assert_equal far_future, project.due_date
896 assert_equal far_future, project.due_date
884 end
897 end
885
898
886 test "#completed_percent with no versions should be 100" do
899 test "#completed_percent with no versions should be 100" do
887 project = Project.generate!
900 project = Project.generate!
888 assert_equal 100, project.completed_percent
901 assert_equal 100, project.completed_percent
889 end
902 end
890
903
891 test "#completed_percent with versions should return 0 if the versions have no issues" do
904 test "#completed_percent with versions should return 0 if the versions have no issues" do
892 project = Project.generate!
905 project = Project.generate!
893 Version.generate!(:project => project)
906 Version.generate!(:project => project)
894 Version.generate!(:project => project)
907 Version.generate!(:project => project)
895
908
896 assert_equal 0, project.completed_percent
909 assert_equal 0, project.completed_percent
897 end
910 end
898
911
899 test "#completed_percent with versions should return 100 if the version has only closed issues" do
912 test "#completed_percent with versions should return 100 if the version has only closed issues" do
900 project = Project.generate!
913 project = Project.generate!
901 project.trackers << Tracker.generate!
914 project.trackers << Tracker.generate!
902 v1 = Version.generate!(:project => project)
915 v1 = Version.generate!(:project => project)
903 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
916 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
904 v2 = Version.generate!(:project => project)
917 v2 = Version.generate!(:project => project)
905 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
918 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
906
919
907 assert_equal 100, project.completed_percent
920 assert_equal 100, project.completed_percent
908 end
921 end
909
922
910 test "#completed_percent with versions should return the averaged completed percent of the versions (not weighted)" do
923 test "#completed_percent with versions should return the averaged completed percent of the versions (not weighted)" do
911 project = Project.generate!
924 project = Project.generate!
912 project.trackers << Tracker.generate!
925 project.trackers << Tracker.generate!
913 v1 = Version.generate!(:project => project)
926 v1 = Version.generate!(:project => project)
914 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
927 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
915 v2 = Version.generate!(:project => project)
928 v2 = Version.generate!(:project => project)
916 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
929 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
917
930
918 assert_equal 50, project.completed_percent
931 assert_equal 50, project.completed_percent
919 end
932 end
920
933
921 test "#notified_users" do
934 test "#notified_users" do
922 project = Project.generate!
935 project = Project.generate!
923 role = Role.generate!
936 role = Role.generate!
924
937
925 user_with_membership_notification = User.generate!(:mail_notification => 'selected')
938 user_with_membership_notification = User.generate!(:mail_notification => 'selected')
926 Member.create!(:project => project, :roles => [role], :principal => user_with_membership_notification, :mail_notification => true)
939 Member.create!(:project => project, :roles => [role], :principal => user_with_membership_notification, :mail_notification => true)
927
940
928 all_events_user = User.generate!(:mail_notification => 'all')
941 all_events_user = User.generate!(:mail_notification => 'all')
929 Member.create!(:project => project, :roles => [role], :principal => all_events_user)
942 Member.create!(:project => project, :roles => [role], :principal => all_events_user)
930
943
931 no_events_user = User.generate!(:mail_notification => 'none')
944 no_events_user = User.generate!(:mail_notification => 'none')
932 Member.create!(:project => project, :roles => [role], :principal => no_events_user)
945 Member.create!(:project => project, :roles => [role], :principal => no_events_user)
933
946
934 only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
947 only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
935 Member.create!(:project => project, :roles => [role], :principal => only_my_events_user)
948 Member.create!(:project => project, :roles => [role], :principal => only_my_events_user)
936
949
937 only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
950 only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
938 Member.create!(:project => project, :roles => [role], :principal => only_assigned_user)
951 Member.create!(:project => project, :roles => [role], :principal => only_assigned_user)
939
952
940 only_owned_user = User.generate!(:mail_notification => 'only_owner')
953 only_owned_user = User.generate!(:mail_notification => 'only_owner')
941 Member.create!(:project => project, :roles => [role], :principal => only_owned_user)
954 Member.create!(:project => project, :roles => [role], :principal => only_owned_user)
942
955
943 assert project.notified_users.include?(user_with_membership_notification), "should include members with a mail notification"
956 assert project.notified_users.include?(user_with_membership_notification), "should include members with a mail notification"
944 assert project.notified_users.include?(all_events_user), "should include users with the 'all' notification option"
957 assert project.notified_users.include?(all_events_user), "should include users with the 'all' notification option"
945 assert !project.notified_users.include?(no_events_user), "should not include users with the 'none' notification option"
958 assert !project.notified_users.include?(no_events_user), "should not include users with the 'none' notification option"
946 assert !project.notified_users.include?(only_my_events_user), "should not include users with the 'only_my_events' notification option"
959 assert !project.notified_users.include?(only_my_events_user), "should not include users with the 'only_my_events' notification option"
947 assert !project.notified_users.include?(only_assigned_user), "should not include users with the 'only_assigned' notification option"
960 assert !project.notified_users.include?(only_assigned_user), "should not include users with the 'only_assigned' notification option"
948 assert !project.notified_users.include?(only_owned_user), "should not include users with the 'only_owner' notification option"
961 assert !project.notified_users.include?(only_owned_user), "should not include users with the 'only_owner' notification option"
949 end
962 end
950
963
951 def test_override_roles_without_builtin_group_memberships
964 def test_override_roles_without_builtin_group_memberships
952 project = Project.generate!
965 project = Project.generate!
953 assert_equal [Role.anonymous], project.override_roles(Role.anonymous)
966 assert_equal [Role.anonymous], project.override_roles(Role.anonymous)
954 assert_equal [Role.non_member], project.override_roles(Role.non_member)
967 assert_equal [Role.non_member], project.override_roles(Role.non_member)
955 end
968 end
956
969
957 def test_css_classes
970 def test_css_classes
958 p = Project.new
971 p = Project.new
959 assert_kind_of String, p.css_classes
972 assert_kind_of String, p.css_classes
960 assert_not_include 'archived', p.css_classes.split
973 assert_not_include 'archived', p.css_classes.split
961 assert_not_include 'closed', p.css_classes.split
974 assert_not_include 'closed', p.css_classes.split
962 end
975 end
963
976
964 def test_css_classes_for_archived_project
977 def test_css_classes_for_archived_project
965 p = Project.new
978 p = Project.new
966 p.status = Project::STATUS_ARCHIVED
979 p.status = Project::STATUS_ARCHIVED
967 assert_include 'archived', p.css_classes.split
980 assert_include 'archived', p.css_classes.split
968 end
981 end
969
982
970 def test_css_classes_for_closed_project
983 def test_css_classes_for_closed_project
971 p = Project.new
984 p = Project.new
972 p.status = Project::STATUS_CLOSED
985 p.status = Project::STATUS_CLOSED
973 assert_include 'closed', p.css_classes.split
986 assert_include 'closed', p.css_classes.split
974 end
987 end
975 end
988 end
General Comments 0
You need to be logged in to leave comments. Login now