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