##// END OF EJS Templates
Clean up sort orders....
Jean-Philippe Lang -
r15939:fe906e4fb6b3
parent child
Show More
@@ -1,1096 +1,1096
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, :dependent => :destroy
40 has_many :versions, :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, :dependent => :delete_all
43 has_many :queries, :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(:name)}, :dependent => :delete_all
47 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
47 has_many :boards, lambda {order(:position)}, :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(: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, :maximum => IDENTIFIER_MAX_LENGTH
75 validates_length_of :identifier, :maximum => 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 # * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query)
176 # * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query)
177 # * :project => project limit the condition to project
177 # * :project => project limit the condition to project
178 # * :with_subprojects => true limit the condition to project and its subprojects
178 # * :with_subprojects => true limit the condition to project and its subprojects
179 # * :member => true limit the condition to the user projects
179 # * :member => true limit the condition to the user projects
180 def self.allowed_to_condition(user, permission, options={})
180 def self.allowed_to_condition(user, permission, options={})
181 perm = Redmine::AccessControl.permission(permission)
181 perm = Redmine::AccessControl.permission(permission)
182 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
182 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
183 if !options[:skip_pre_condition] && perm && perm.project_module
183 if !options[:skip_pre_condition] && perm && perm.project_module
184 # If the permission belongs to a project module, make sure the module is enabled
184 # If the permission belongs to a project module, make sure the module is enabled
185 base_statement << " AND EXISTS (SELECT 1 AS one FROM #{EnabledModule.table_name} em WHERE em.project_id = #{Project.table_name}.id AND em.name='#{perm.project_module}')"
185 base_statement << " AND EXISTS (SELECT 1 AS one FROM #{EnabledModule.table_name} em WHERE em.project_id = #{Project.table_name}.id AND em.name='#{perm.project_module}')"
186 end
186 end
187 if project = options[:project]
187 if project = options[:project]
188 project_statement = project.project_condition(options[:with_subprojects])
188 project_statement = project.project_condition(options[:with_subprojects])
189 base_statement = "(#{project_statement}) AND (#{base_statement})"
189 base_statement = "(#{project_statement}) AND (#{base_statement})"
190 end
190 end
191
191
192 if user.admin?
192 if user.admin?
193 base_statement
193 base_statement
194 else
194 else
195 statement_by_role = {}
195 statement_by_role = {}
196 unless options[:member]
196 unless options[:member]
197 role = user.builtin_role
197 role = user.builtin_role
198 if role.allowed_to?(permission)
198 if role.allowed_to?(permission)
199 s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
199 s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
200 if user.id
200 if user.id
201 group = role.anonymous? ? Group.anonymous : Group.non_member
201 group = role.anonymous? ? Group.anonymous : Group.non_member
202 principal_ids = [user.id, group.id].compact
202 principal_ids = [user.id, group.id].compact
203 s = "(#{s} AND #{Project.table_name}.id NOT IN (SELECT project_id FROM #{Member.table_name} WHERE user_id IN (#{principal_ids.join(',')})))"
203 s = "(#{s} AND #{Project.table_name}.id NOT IN (SELECT project_id FROM #{Member.table_name} WHERE user_id IN (#{principal_ids.join(',')})))"
204 end
204 end
205 statement_by_role[role] = s
205 statement_by_role[role] = s
206 end
206 end
207 end
207 end
208 user.project_ids_by_role.each do |role, project_ids|
208 user.project_ids_by_role.each do |role, project_ids|
209 if role.allowed_to?(permission) && project_ids.any?
209 if role.allowed_to?(permission) && project_ids.any?
210 statement_by_role[role] = "#{Project.table_name}.id IN (#{project_ids.join(',')})"
210 statement_by_role[role] = "#{Project.table_name}.id IN (#{project_ids.join(',')})"
211 end
211 end
212 end
212 end
213 if statement_by_role.empty?
213 if statement_by_role.empty?
214 "1=0"
214 "1=0"
215 else
215 else
216 if block_given?
216 if block_given?
217 statement_by_role.each do |role, statement|
217 statement_by_role.each do |role, statement|
218 if s = yield(role, user)
218 if s = yield(role, user)
219 statement_by_role[role] = "(#{statement} AND (#{s}))"
219 statement_by_role[role] = "(#{statement} AND (#{s}))"
220 end
220 end
221 end
221 end
222 end
222 end
223 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
223 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
224 end
224 end
225 end
225 end
226 end
226 end
227
227
228 def override_roles(role)
228 def override_roles(role)
229 @override_members ||= memberships.
229 @override_members ||= memberships.
230 joins(:principal).
230 joins(:principal).
231 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
231 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
232
232
233 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
233 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
234 member = @override_members.detect {|m| m.principal.is_a? group_class}
234 member = @override_members.detect {|m| m.principal.is_a? group_class}
235 member ? member.roles.to_a : [role]
235 member ? member.roles.to_a : [role]
236 end
236 end
237
237
238 def principals
238 def principals
239 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
239 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
240 end
240 end
241
241
242 def users
242 def users
243 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
243 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
244 end
244 end
245
245
246 # Returns the Systemwide and project specific activities
246 # Returns the Systemwide and project specific activities
247 def activities(include_inactive=false)
247 def activities(include_inactive=false)
248 t = TimeEntryActivity.table_name
248 t = TimeEntryActivity.table_name
249 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
249 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
250
250
251 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
251 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
252 if overridden_activity_ids.any?
252 if overridden_activity_ids.any?
253 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
253 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
254 end
254 end
255 unless include_inactive
255 unless include_inactive
256 scope = scope.active
256 scope = scope.active
257 end
257 end
258 scope
258 scope
259 end
259 end
260
260
261 # Will create a new Project specific Activity or update an existing one
261 # Will create a new Project specific Activity or update an existing one
262 #
262 #
263 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
263 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
264 # does not successfully save.
264 # does not successfully save.
265 def update_or_create_time_entry_activity(id, activity_hash)
265 def update_or_create_time_entry_activity(id, activity_hash)
266 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
266 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
267 self.create_time_entry_activity_if_needed(activity_hash)
267 self.create_time_entry_activity_if_needed(activity_hash)
268 else
268 else
269 activity = project.time_entry_activities.find_by_id(id.to_i)
269 activity = project.time_entry_activities.find_by_id(id.to_i)
270 activity.update_attributes(activity_hash) if activity
270 activity.update_attributes(activity_hash) if activity
271 end
271 end
272 end
272 end
273
273
274 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
274 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
275 #
275 #
276 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
276 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
277 # does not successfully save.
277 # does not successfully save.
278 def create_time_entry_activity_if_needed(activity)
278 def create_time_entry_activity_if_needed(activity)
279 if activity['parent_id']
279 if activity['parent_id']
280 parent_activity = TimeEntryActivity.find(activity['parent_id'])
280 parent_activity = TimeEntryActivity.find(activity['parent_id'])
281 activity['name'] = parent_activity.name
281 activity['name'] = parent_activity.name
282 activity['position'] = parent_activity.position
282 activity['position'] = parent_activity.position
283 if Enumeration.overriding_change?(activity, parent_activity)
283 if Enumeration.overriding_change?(activity, parent_activity)
284 project_activity = self.time_entry_activities.create(activity)
284 project_activity = self.time_entry_activities.create(activity)
285 if project_activity.new_record?
285 if project_activity.new_record?
286 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
286 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
287 else
287 else
288 self.time_entries.
288 self.time_entries.
289 where(:activity_id => parent_activity.id).
289 where(:activity_id => parent_activity.id).
290 update_all(:activity_id => project_activity.id)
290 update_all(:activity_id => project_activity.id)
291 end
291 end
292 end
292 end
293 end
293 end
294 end
294 end
295
295
296 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
296 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
297 #
297 #
298 # Examples:
298 # Examples:
299 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
299 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
300 # project.project_condition(false) => "projects.id = 1"
300 # project.project_condition(false) => "projects.id = 1"
301 def project_condition(with_subprojects)
301 def project_condition(with_subprojects)
302 cond = "#{Project.table_name}.id = #{id}"
302 cond = "#{Project.table_name}.id = #{id}"
303 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
303 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
304 cond
304 cond
305 end
305 end
306
306
307 def self.find(*args)
307 def self.find(*args)
308 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
308 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
309 project = find_by_identifier(*args)
309 project = find_by_identifier(*args)
310 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
310 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
311 project
311 project
312 else
312 else
313 super
313 super
314 end
314 end
315 end
315 end
316
316
317 def self.find_by_param(*args)
317 def self.find_by_param(*args)
318 self.find(*args)
318 self.find(*args)
319 end
319 end
320
320
321 alias :base_reload :reload
321 alias :base_reload :reload
322 def reload(*args)
322 def reload(*args)
323 @principals = nil
323 @principals = nil
324 @users = nil
324 @users = nil
325 @shared_versions = nil
325 @shared_versions = nil
326 @rolled_up_versions = nil
326 @rolled_up_versions = nil
327 @rolled_up_trackers = nil
327 @rolled_up_trackers = nil
328 @all_issue_custom_fields = nil
328 @all_issue_custom_fields = nil
329 @all_time_entry_custom_fields = nil
329 @all_time_entry_custom_fields = nil
330 @to_param = nil
330 @to_param = nil
331 @allowed_parents = nil
331 @allowed_parents = nil
332 @allowed_permissions = nil
332 @allowed_permissions = nil
333 @actions_allowed = nil
333 @actions_allowed = nil
334 @start_date = nil
334 @start_date = nil
335 @due_date = nil
335 @due_date = nil
336 @override_members = nil
336 @override_members = nil
337 @assignable_users = nil
337 @assignable_users = nil
338 base_reload(*args)
338 base_reload(*args)
339 end
339 end
340
340
341 def to_param
341 def to_param
342 if new_record?
342 if new_record?
343 nil
343 nil
344 else
344 else
345 # id is used for projects with a numeric identifier (compatibility)
345 # id is used for projects with a numeric identifier (compatibility)
346 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
346 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
347 end
347 end
348 end
348 end
349
349
350 def active?
350 def active?
351 self.status == STATUS_ACTIVE
351 self.status == STATUS_ACTIVE
352 end
352 end
353
353
354 def closed?
354 def closed?
355 self.status == STATUS_CLOSED
355 self.status == STATUS_CLOSED
356 end
356 end
357
357
358 def archived?
358 def archived?
359 self.status == STATUS_ARCHIVED
359 self.status == STATUS_ARCHIVED
360 end
360 end
361
361
362 # Archives the project and its descendants
362 # Archives the project and its descendants
363 def archive
363 def archive
364 # Check that there is no issue of a non descendant project that is assigned
364 # Check that there is no issue of a non descendant project that is assigned
365 # to one of the project or descendant versions
365 # to one of the project or descendant versions
366 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
366 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
367
367
368 if version_ids.any? &&
368 if version_ids.any? &&
369 Issue.
369 Issue.
370 includes(:project).
370 includes(:project).
371 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
371 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
372 where(:fixed_version_id => version_ids).
372 where(:fixed_version_id => version_ids).
373 exists?
373 exists?
374 return false
374 return false
375 end
375 end
376 Project.transaction do
376 Project.transaction do
377 archive!
377 archive!
378 end
378 end
379 true
379 true
380 end
380 end
381
381
382 # Unarchives the project
382 # Unarchives the project
383 # All its ancestors must be active
383 # All its ancestors must be active
384 def unarchive
384 def unarchive
385 return false if ancestors.detect {|a| a.archived?}
385 return false if ancestors.detect {|a| a.archived?}
386 new_status = STATUS_ACTIVE
386 new_status = STATUS_ACTIVE
387 if parent
387 if parent
388 new_status = parent.status
388 new_status = parent.status
389 end
389 end
390 update_attribute :status, new_status
390 update_attribute :status, new_status
391 end
391 end
392
392
393 def close
393 def close
394 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
394 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
395 end
395 end
396
396
397 def reopen
397 def reopen
398 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
398 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
399 end
399 end
400
400
401 # Returns an array of projects the project can be moved to
401 # Returns an array of projects the project can be moved to
402 # by the current user
402 # by the current user
403 def allowed_parents(user=User.current)
403 def allowed_parents(user=User.current)
404 return @allowed_parents if @allowed_parents
404 return @allowed_parents if @allowed_parents
405 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
405 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
406 @allowed_parents = @allowed_parents - self_and_descendants
406 @allowed_parents = @allowed_parents - self_and_descendants
407 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
407 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
408 @allowed_parents << nil
408 @allowed_parents << nil
409 end
409 end
410 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
410 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
411 @allowed_parents << parent
411 @allowed_parents << parent
412 end
412 end
413 @allowed_parents
413 @allowed_parents
414 end
414 end
415
415
416 # Sets the parent of the project with authorization check
416 # Sets the parent of the project with authorization check
417 def set_allowed_parent!(p)
417 def set_allowed_parent!(p)
418 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
418 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
419 p = p.id if p.is_a?(Project)
419 p = p.id if p.is_a?(Project)
420 send :safe_attributes, {:project_id => p}
420 send :safe_attributes, {:project_id => p}
421 save
421 save
422 end
422 end
423
423
424 # Sets the parent of the project and saves the project
424 # Sets the parent of the project and saves the project
425 # Argument can be either a Project, a String, a Fixnum or nil
425 # Argument can be either a Project, a String, a Fixnum or nil
426 def set_parent!(p)
426 def set_parent!(p)
427 if p.is_a?(Project)
427 if p.is_a?(Project)
428 self.parent = p
428 self.parent = p
429 else
429 else
430 self.parent_id = p
430 self.parent_id = p
431 end
431 end
432 save
432 save
433 end
433 end
434
434
435 # Returns a scope of the trackers used by the project and its active sub projects
435 # Returns a scope of the trackers used by the project and its active sub projects
436 def rolled_up_trackers(include_subprojects=true)
436 def rolled_up_trackers(include_subprojects=true)
437 if include_subprojects
437 if include_subprojects
438 @rolled_up_trackers ||= rolled_up_trackers_base_scope.
438 @rolled_up_trackers ||= rolled_up_trackers_base_scope.
439 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt)
439 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt)
440 else
440 else
441 rolled_up_trackers_base_scope.
441 rolled_up_trackers_base_scope.
442 where(:projects => {:id => id})
442 where(:projects => {:id => id})
443 end
443 end
444 end
444 end
445
445
446 def rolled_up_trackers_base_scope
446 def rolled_up_trackers_base_scope
447 Tracker.
447 Tracker.
448 joins(projects: :enabled_modules).
448 joins(projects: :enabled_modules).
449 where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED).
449 where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED).
450 where(:enabled_modules => {:name => 'issue_tracking'}).
450 where(:enabled_modules => {:name => 'issue_tracking'}).
451 distinct.
451 distinct.
452 sorted
452 sorted
453 end
453 end
454
454
455 # Closes open and locked project versions that are completed
455 # Closes open and locked project versions that are completed
456 def close_completed_versions
456 def close_completed_versions
457 Version.transaction do
457 Version.transaction do
458 versions.where(:status => %w(open locked)).each do |version|
458 versions.where(:status => %w(open locked)).each do |version|
459 if version.completed?
459 if version.completed?
460 version.update_attribute(:status, 'closed')
460 version.update_attribute(:status, 'closed')
461 end
461 end
462 end
462 end
463 end
463 end
464 end
464 end
465
465
466 # Returns a scope of the Versions on subprojects
466 # Returns a scope of the Versions on subprojects
467 def rolled_up_versions
467 def rolled_up_versions
468 @rolled_up_versions ||=
468 @rolled_up_versions ||=
469 Version.
469 Version.
470 joins(:project).
470 joins(:project).
471 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
471 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
472 end
472 end
473
473
474 # Returns a scope of the Versions used by the project
474 # Returns a scope of the Versions used by the project
475 def shared_versions
475 def shared_versions
476 if new_record?
476 if new_record?
477 Version.
477 Version.
478 joins(:project).
478 joins(:project).
479 preload(:project).
479 preload(:project).
480 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
480 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
481 else
481 else
482 @shared_versions ||= begin
482 @shared_versions ||= begin
483 r = root? ? self : root
483 r = root? ? self : root
484 Version.
484 Version.
485 joins(:project).
485 joins(:project).
486 preload(:project).
486 preload(:project).
487 where("#{Project.table_name}.id = #{id}" +
487 where("#{Project.table_name}.id = #{id}" +
488 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
488 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
489 " #{Version.table_name}.sharing = 'system'" +
489 " #{Version.table_name}.sharing = 'system'" +
490 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
490 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
491 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
491 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
492 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
492 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
493 "))")
493 "))")
494 end
494 end
495 end
495 end
496 end
496 end
497
497
498 # Returns a hash of project users grouped by role
498 # Returns a hash of project users grouped by role
499 def users_by_role
499 def users_by_role
500 members.includes(:user, :roles).inject({}) do |h, m|
500 members.includes(:user, :roles).inject({}) do |h, m|
501 m.roles.each do |r|
501 m.roles.each do |r|
502 h[r] ||= []
502 h[r] ||= []
503 h[r] << m.user
503 h[r] << m.user
504 end
504 end
505 h
505 h
506 end
506 end
507 end
507 end
508
508
509 # Adds user as a project member with the default role
509 # Adds user as a project member with the default role
510 # Used for when a non-admin user creates a project
510 # Used for when a non-admin user creates a project
511 def add_default_member(user)
511 def add_default_member(user)
512 role = self.class.default_member_role
512 role = self.class.default_member_role
513 member = Member.new(:project => self, :principal => user, :roles => [role])
513 member = Member.new(:project => self, :principal => user, :roles => [role])
514 self.members << member
514 self.members << member
515 member
515 member
516 end
516 end
517
517
518 # Default role that is given to non-admin users that
518 # Default role that is given to non-admin users that
519 # create a project
519 # create a project
520 def self.default_member_role
520 def self.default_member_role
521 Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
521 Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
522 end
522 end
523
523
524 # Deletes all project's members
524 # Deletes all project's members
525 def delete_all_members
525 def delete_all_members
526 me, mr = Member.table_name, MemberRole.table_name
526 me, mr = Member.table_name, MemberRole.table_name
527 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
527 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
528 Member.where(:project_id => id).delete_all
528 Member.where(:project_id => id).delete_all
529 end
529 end
530
530
531 # Return a Principal scope of users/groups issues can be assigned to
531 # Return a Principal scope of users/groups issues can be assigned to
532 def assignable_users(tracker=nil)
532 def assignable_users(tracker=nil)
533 return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker]
533 return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker]
534
534
535 types = ['User']
535 types = ['User']
536 types << 'Group' if Setting.issue_group_assignment?
536 types << 'Group' if Setting.issue_group_assignment?
537
537
538 scope = Principal.
538 scope = Principal.
539 active.
539 active.
540 joins(:members => :roles).
540 joins(:members => :roles).
541 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
541 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
542 distinct.
542 distinct.
543 sorted
543 sorted
544
544
545 if tracker
545 if tracker
546 # Rejects users that cannot the view the tracker
546 # Rejects users that cannot the view the tracker
547 roles = Role.where(:assignable => true).select {|role| role.permissions_tracker?(:view_issues, tracker)}
547 roles = Role.where(:assignable => true).select {|role| role.permissions_tracker?(:view_issues, tracker)}
548 scope = scope.where(:roles => {:id => roles.map(&:id)})
548 scope = scope.where(:roles => {:id => roles.map(&:id)})
549 end
549 end
550
550
551 @assignable_users ||= {}
551 @assignable_users ||= {}
552 @assignable_users[tracker] = scope
552 @assignable_users[tracker] = scope
553 end
553 end
554
554
555 # Returns the mail addresses of users that should be always notified on project events
555 # Returns the mail addresses of users that should be always notified on project events
556 def recipients
556 def recipients
557 notified_users.collect {|user| user.mail}
557 notified_users.collect {|user| user.mail}
558 end
558 end
559
559
560 # Returns the users that should be notified on project events
560 # Returns the users that should be notified on project events
561 def notified_users
561 def notified_users
562 # TODO: User part should be extracted to User#notify_about?
562 # TODO: User part should be extracted to User#notify_about?
563 members.preload(:principal).select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
563 members.preload(:principal).select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
564 end
564 end
565
565
566 # Returns a scope of all custom fields enabled for project issues
566 # Returns a scope of all custom fields enabled for project issues
567 # (explicitly associated custom fields and custom fields enabled for all projects)
567 # (explicitly associated custom fields and custom fields enabled for all projects)
568 def all_issue_custom_fields
568 def all_issue_custom_fields
569 if new_record?
569 if new_record?
570 @all_issue_custom_fields ||= IssueCustomField.
570 @all_issue_custom_fields ||= IssueCustomField.
571 sorted.
571 sorted.
572 where("is_for_all = ? OR id IN (?)", true, issue_custom_field_ids)
572 where("is_for_all = ? OR id IN (?)", true, issue_custom_field_ids)
573 else
573 else
574 @all_issue_custom_fields ||= IssueCustomField.
574 @all_issue_custom_fields ||= IssueCustomField.
575 sorted.
575 sorted.
576 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
576 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
577 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
577 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
578 " WHERE cfp.project_id = ?)", true, id)
578 " WHERE cfp.project_id = ?)", true, id)
579 end
579 end
580 end
580 end
581
581
582 def project
582 def project
583 self
583 self
584 end
584 end
585
585
586 def <=>(project)
586 def <=>(project)
587 name.casecmp(project.name)
587 name.casecmp(project.name)
588 end
588 end
589
589
590 def to_s
590 def to_s
591 name
591 name
592 end
592 end
593
593
594 # Returns a short description of the projects (first lines)
594 # Returns a short description of the projects (first lines)
595 def short_description(length = 255)
595 def short_description(length = 255)
596 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
596 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
597 end
597 end
598
598
599 def css_classes
599 def css_classes
600 s = 'project'
600 s = 'project'
601 s << ' root' if root?
601 s << ' root' if root?
602 s << ' child' if child?
602 s << ' child' if child?
603 s << (leaf? ? ' leaf' : ' parent')
603 s << (leaf? ? ' leaf' : ' parent')
604 unless active?
604 unless active?
605 if archived?
605 if archived?
606 s << ' archived'
606 s << ' archived'
607 else
607 else
608 s << ' closed'
608 s << ' closed'
609 end
609 end
610 end
610 end
611 s
611 s
612 end
612 end
613
613
614 # The earliest start date of a project, based on it's issues and versions
614 # The earliest start date of a project, based on it's issues and versions
615 def start_date
615 def start_date
616 @start_date ||= [
616 @start_date ||= [
617 issues.minimum('start_date'),
617 issues.minimum('start_date'),
618 shared_versions.minimum('effective_date'),
618 shared_versions.minimum('effective_date'),
619 Issue.fixed_version(shared_versions).minimum('start_date')
619 Issue.fixed_version(shared_versions).minimum('start_date')
620 ].compact.min
620 ].compact.min
621 end
621 end
622
622
623 # The latest due date of an issue or version
623 # The latest due date of an issue or version
624 def due_date
624 def due_date
625 @due_date ||= [
625 @due_date ||= [
626 issues.maximum('due_date'),
626 issues.maximum('due_date'),
627 shared_versions.maximum('effective_date'),
627 shared_versions.maximum('effective_date'),
628 Issue.fixed_version(shared_versions).maximum('due_date')
628 Issue.fixed_version(shared_versions).maximum('due_date')
629 ].compact.max
629 ].compact.max
630 end
630 end
631
631
632 def overdue?
632 def overdue?
633 active? && !due_date.nil? && (due_date < User.current.today)
633 active? && !due_date.nil? && (due_date < User.current.today)
634 end
634 end
635
635
636 # Returns the percent completed for this project, based on the
636 # Returns the percent completed for this project, based on the
637 # progress on it's versions.
637 # progress on it's versions.
638 def completed_percent(options={:include_subprojects => false})
638 def completed_percent(options={:include_subprojects => false})
639 if options.delete(:include_subprojects)
639 if options.delete(:include_subprojects)
640 total = self_and_descendants.collect(&:completed_percent).sum
640 total = self_and_descendants.collect(&:completed_percent).sum
641
641
642 total / self_and_descendants.count
642 total / self_and_descendants.count
643 else
643 else
644 if versions.count > 0
644 if versions.count > 0
645 total = versions.collect(&:completed_percent).sum
645 total = versions.collect(&:completed_percent).sum
646
646
647 total / versions.count
647 total / versions.count
648 else
648 else
649 100
649 100
650 end
650 end
651 end
651 end
652 end
652 end
653
653
654 # Return true if this project allows to do the specified action.
654 # Return true if this project allows to do the specified action.
655 # action can be:
655 # action can be:
656 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
656 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
657 # * a permission Symbol (eg. :edit_project)
657 # * a permission Symbol (eg. :edit_project)
658 def allows_to?(action)
658 def allows_to?(action)
659 if archived?
659 if archived?
660 # No action allowed on archived projects
660 # No action allowed on archived projects
661 return false
661 return false
662 end
662 end
663 unless active? || Redmine::AccessControl.read_action?(action)
663 unless active? || Redmine::AccessControl.read_action?(action)
664 # No write action allowed on closed projects
664 # No write action allowed on closed projects
665 return false
665 return false
666 end
666 end
667 # No action allowed on disabled modules
667 # No action allowed on disabled modules
668 if action.is_a? Hash
668 if action.is_a? Hash
669 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
669 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
670 else
670 else
671 allowed_permissions.include? action
671 allowed_permissions.include? action
672 end
672 end
673 end
673 end
674
674
675 # Return the enabled module with the given name
675 # Return the enabled module with the given name
676 # or nil if the module is not enabled for the project
676 # or nil if the module is not enabled for the project
677 def enabled_module(name)
677 def enabled_module(name)
678 name = name.to_s
678 name = name.to_s
679 enabled_modules.detect {|m| m.name == name}
679 enabled_modules.detect {|m| m.name == name}
680 end
680 end
681
681
682 # Return true if the module with the given name is enabled
682 # Return true if the module with the given name is enabled
683 def module_enabled?(name)
683 def module_enabled?(name)
684 enabled_module(name).present?
684 enabled_module(name).present?
685 end
685 end
686
686
687 def enabled_module_names=(module_names)
687 def enabled_module_names=(module_names)
688 if module_names && module_names.is_a?(Array)
688 if module_names && module_names.is_a?(Array)
689 module_names = module_names.collect(&:to_s).reject(&:blank?)
689 module_names = module_names.collect(&:to_s).reject(&:blank?)
690 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
690 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
691 else
691 else
692 enabled_modules.clear
692 enabled_modules.clear
693 end
693 end
694 end
694 end
695
695
696 # Returns an array of the enabled modules names
696 # Returns an array of the enabled modules names
697 def enabled_module_names
697 def enabled_module_names
698 enabled_modules.collect(&:name)
698 enabled_modules.collect(&:name)
699 end
699 end
700
700
701 # Enable a specific module
701 # Enable a specific module
702 #
702 #
703 # Examples:
703 # Examples:
704 # project.enable_module!(:issue_tracking)
704 # project.enable_module!(:issue_tracking)
705 # project.enable_module!("issue_tracking")
705 # project.enable_module!("issue_tracking")
706 def enable_module!(name)
706 def enable_module!(name)
707 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
707 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
708 end
708 end
709
709
710 # Disable a module if it exists
710 # Disable a module if it exists
711 #
711 #
712 # Examples:
712 # Examples:
713 # project.disable_module!(:issue_tracking)
713 # project.disable_module!(:issue_tracking)
714 # project.disable_module!("issue_tracking")
714 # project.disable_module!("issue_tracking")
715 # project.disable_module!(project.enabled_modules.first)
715 # project.disable_module!(project.enabled_modules.first)
716 def disable_module!(target)
716 def disable_module!(target)
717 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
717 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
718 target.destroy unless target.blank?
718 target.destroy unless target.blank?
719 end
719 end
720
720
721 safe_attributes 'name',
721 safe_attributes 'name',
722 'description',
722 'description',
723 'homepage',
723 'homepage',
724 'is_public',
724 'is_public',
725 'identifier',
725 'identifier',
726 'custom_field_values',
726 'custom_field_values',
727 'custom_fields',
727 'custom_fields',
728 'tracker_ids',
728 'tracker_ids',
729 'issue_custom_field_ids',
729 'issue_custom_field_ids',
730 'parent_id',
730 'parent_id',
731 'default_version_id'
731 'default_version_id'
732
732
733 safe_attributes 'enabled_module_names',
733 safe_attributes 'enabled_module_names',
734 :if => lambda {|project, user|
734 :if => lambda {|project, user|
735 if project.new_record?
735 if project.new_record?
736 if user.admin?
736 if user.admin?
737 true
737 true
738 else
738 else
739 default_member_role.has_permission?(:select_project_modules)
739 default_member_role.has_permission?(:select_project_modules)
740 end
740 end
741 else
741 else
742 user.allowed_to?(:select_project_modules, project)
742 user.allowed_to?(:select_project_modules, project)
743 end
743 end
744 }
744 }
745
745
746 safe_attributes 'inherit_members',
746 safe_attributes 'inherit_members',
747 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
747 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
748
748
749 def safe_attributes=(attrs, user=User.current)
749 def safe_attributes=(attrs, user=User.current)
750 return unless attrs.is_a?(Hash)
750 return unless attrs.is_a?(Hash)
751 attrs = attrs.deep_dup
751 attrs = attrs.deep_dup
752
752
753 @unallowed_parent_id = nil
753 @unallowed_parent_id = nil
754 if new_record? || attrs.key?('parent_id')
754 if new_record? || attrs.key?('parent_id')
755 parent_id_param = attrs['parent_id'].to_s
755 parent_id_param = attrs['parent_id'].to_s
756 if new_record? || parent_id_param != parent_id.to_s
756 if new_record? || parent_id_param != parent_id.to_s
757 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
757 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
758 unless allowed_parents(user).include?(p)
758 unless allowed_parents(user).include?(p)
759 attrs.delete('parent_id')
759 attrs.delete('parent_id')
760 @unallowed_parent_id = true
760 @unallowed_parent_id = true
761 end
761 end
762 end
762 end
763 end
763 end
764
764
765 super(attrs, user)
765 super(attrs, user)
766 end
766 end
767
767
768 # Returns an auto-generated project identifier based on the last identifier used
768 # Returns an auto-generated project identifier based on the last identifier used
769 def self.next_identifier
769 def self.next_identifier
770 p = Project.order('id DESC').first
770 p = Project.order('id DESC').first
771 p.nil? ? nil : p.identifier.to_s.succ
771 p.nil? ? nil : p.identifier.to_s.succ
772 end
772 end
773
773
774 # Copies and saves the Project instance based on the +project+.
774 # Copies and saves the Project instance based on the +project+.
775 # Duplicates the source project's:
775 # Duplicates the source project's:
776 # * Wiki
776 # * Wiki
777 # * Versions
777 # * Versions
778 # * Categories
778 # * Categories
779 # * Issues
779 # * Issues
780 # * Members
780 # * Members
781 # * Queries
781 # * Queries
782 #
782 #
783 # Accepts an +options+ argument to specify what to copy
783 # Accepts an +options+ argument to specify what to copy
784 #
784 #
785 # Examples:
785 # Examples:
786 # project.copy(1) # => copies everything
786 # project.copy(1) # => copies everything
787 # project.copy(1, :only => 'members') # => copies members only
787 # project.copy(1, :only => 'members') # => copies members only
788 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
788 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
789 def copy(project, options={})
789 def copy(project, options={})
790 project = project.is_a?(Project) ? project : Project.find(project)
790 project = project.is_a?(Project) ? project : Project.find(project)
791
791
792 to_be_copied = %w(members wiki versions issue_categories issues queries boards)
792 to_be_copied = %w(members wiki versions issue_categories issues queries boards)
793 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
793 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
794
794
795 Project.transaction do
795 Project.transaction do
796 if save
796 if save
797 reload
797 reload
798 to_be_copied.each do |name|
798 to_be_copied.each do |name|
799 send "copy_#{name}", project
799 send "copy_#{name}", project
800 end
800 end
801 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
801 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
802 save
802 save
803 else
803 else
804 false
804 false
805 end
805 end
806 end
806 end
807 end
807 end
808
808
809 def member_principals
809 def member_principals
810 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
810 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
811 memberships.active
811 memberships.active
812 end
812 end
813
813
814 # Returns a new unsaved Project instance with attributes copied from +project+
814 # Returns a new unsaved Project instance with attributes copied from +project+
815 def self.copy_from(project)
815 def self.copy_from(project)
816 project = project.is_a?(Project) ? project : Project.find(project)
816 project = project.is_a?(Project) ? project : Project.find(project)
817 # clear unique attributes
817 # clear unique attributes
818 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
818 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
819 copy = Project.new(attributes)
819 copy = Project.new(attributes)
820 copy.enabled_module_names = project.enabled_module_names
820 copy.enabled_module_names = project.enabled_module_names
821 copy.trackers = project.trackers
821 copy.trackers = project.trackers
822 copy.custom_values = project.custom_values.collect {|v| v.clone}
822 copy.custom_values = project.custom_values.collect {|v| v.clone}
823 copy.issue_custom_fields = project.issue_custom_fields
823 copy.issue_custom_fields = project.issue_custom_fields
824 copy
824 copy
825 end
825 end
826
826
827 # Yields the given block for each project with its level in the tree
827 # Yields the given block for each project with its level in the tree
828 def self.project_tree(projects, options={}, &block)
828 def self.project_tree(projects, options={}, &block)
829 ancestors = []
829 ancestors = []
830 if options[:init_level] && projects.first
830 if options[:init_level] && projects.first
831 ancestors = projects.first.ancestors.to_a
831 ancestors = projects.first.ancestors.to_a
832 end
832 end
833 projects.sort_by(&:lft).each do |project|
833 projects.sort_by(&:lft).each do |project|
834 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
834 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
835 ancestors.pop
835 ancestors.pop
836 end
836 end
837 yield project, ancestors.size
837 yield project, ancestors.size
838 ancestors << project
838 ancestors << project
839 end
839 end
840 end
840 end
841
841
842 private
842 private
843
843
844 def update_inherited_members
844 def update_inherited_members
845 if parent
845 if parent
846 if inherit_members? && !inherit_members_was
846 if inherit_members? && !inherit_members_was
847 remove_inherited_member_roles
847 remove_inherited_member_roles
848 add_inherited_member_roles
848 add_inherited_member_roles
849 elsif !inherit_members? && inherit_members_was
849 elsif !inherit_members? && inherit_members_was
850 remove_inherited_member_roles
850 remove_inherited_member_roles
851 end
851 end
852 end
852 end
853 end
853 end
854
854
855 def remove_inherited_member_roles
855 def remove_inherited_member_roles
856 member_roles = MemberRole.where(:member_id => membership_ids).to_a
856 member_roles = MemberRole.where(:member_id => membership_ids).to_a
857 member_role_ids = member_roles.map(&:id)
857 member_role_ids = member_roles.map(&:id)
858 member_roles.each do |member_role|
858 member_roles.each do |member_role|
859 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
859 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
860 member_role.destroy
860 member_role.destroy
861 end
861 end
862 end
862 end
863 end
863 end
864
864
865 def add_inherited_member_roles
865 def add_inherited_member_roles
866 if inherit_members? && parent
866 if inherit_members? && parent
867 parent.memberships.each do |parent_member|
867 parent.memberships.each do |parent_member|
868 member = Member.find_or_new(self.id, parent_member.user_id)
868 member = Member.find_or_new(self.id, parent_member.user_id)
869 parent_member.member_roles.each do |parent_member_role|
869 parent_member.member_roles.each do |parent_member_role|
870 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
870 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
871 end
871 end
872 member.save!
872 member.save!
873 end
873 end
874 memberships.reset
874 memberships.reset
875 end
875 end
876 end
876 end
877
877
878 def update_versions_from_hierarchy_change
878 def update_versions_from_hierarchy_change
879 Issue.update_versions_from_hierarchy_change(self)
879 Issue.update_versions_from_hierarchy_change(self)
880 end
880 end
881
881
882 def validate_parent
882 def validate_parent
883 if @unallowed_parent_id
883 if @unallowed_parent_id
884 errors.add(:parent_id, :invalid)
884 errors.add(:parent_id, :invalid)
885 elsif parent_id_changed?
885 elsif parent_id_changed?
886 unless parent.nil? || (parent.active? && move_possible?(parent))
886 unless parent.nil? || (parent.active? && move_possible?(parent))
887 errors.add(:parent_id, :invalid)
887 errors.add(:parent_id, :invalid)
888 end
888 end
889 end
889 end
890 end
890 end
891
891
892 # Copies wiki from +project+
892 # Copies wiki from +project+
893 def copy_wiki(project)
893 def copy_wiki(project)
894 # Check that the source project has a wiki first
894 # Check that the source project has a wiki first
895 unless project.wiki.nil?
895 unless project.wiki.nil?
896 wiki = self.wiki || Wiki.new
896 wiki = self.wiki || Wiki.new
897 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
897 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
898 wiki_pages_map = {}
898 wiki_pages_map = {}
899 project.wiki.pages.each do |page|
899 project.wiki.pages.each do |page|
900 # Skip pages without content
900 # Skip pages without content
901 next if page.content.nil?
901 next if page.content.nil?
902 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
902 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
903 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
903 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
904 new_wiki_page.content = new_wiki_content
904 new_wiki_page.content = new_wiki_content
905 wiki.pages << new_wiki_page
905 wiki.pages << new_wiki_page
906 wiki_pages_map[page.id] = new_wiki_page
906 wiki_pages_map[page.id] = new_wiki_page
907 end
907 end
908
908
909 self.wiki = wiki
909 self.wiki = wiki
910 wiki.save
910 wiki.save
911 # Reproduce page hierarchy
911 # Reproduce page hierarchy
912 project.wiki.pages.each do |page|
912 project.wiki.pages.each do |page|
913 if page.parent_id && wiki_pages_map[page.id]
913 if page.parent_id && wiki_pages_map[page.id]
914 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
914 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
915 wiki_pages_map[page.id].save
915 wiki_pages_map[page.id].save
916 end
916 end
917 end
917 end
918 end
918 end
919 end
919 end
920
920
921 # Copies versions from +project+
921 # Copies versions from +project+
922 def copy_versions(project)
922 def copy_versions(project)
923 project.versions.each do |version|
923 project.versions.each do |version|
924 new_version = Version.new
924 new_version = Version.new
925 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
925 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
926 self.versions << new_version
926 self.versions << new_version
927 end
927 end
928 end
928 end
929
929
930 # Copies issue categories from +project+
930 # Copies issue categories from +project+
931 def copy_issue_categories(project)
931 def copy_issue_categories(project)
932 project.issue_categories.each do |issue_category|
932 project.issue_categories.each do |issue_category|
933 new_issue_category = IssueCategory.new
933 new_issue_category = IssueCategory.new
934 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
934 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
935 self.issue_categories << new_issue_category
935 self.issue_categories << new_issue_category
936 end
936 end
937 end
937 end
938
938
939 # Copies issues from +project+
939 # Copies issues from +project+
940 def copy_issues(project)
940 def copy_issues(project)
941 # Stores the source issue id as a key and the copied issues as the
941 # Stores the source issue id as a key and the copied issues as the
942 # value. Used to map the two together for issue relations.
942 # value. Used to map the two together for issue relations.
943 issues_map = {}
943 issues_map = {}
944
944
945 # Store status and reopen locked/closed versions
945 # Store status and reopen locked/closed versions
946 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
946 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
947 version_statuses.each do |version, status|
947 version_statuses.each do |version, status|
948 version.update_attribute :status, 'open'
948 version.update_attribute :status, 'open'
949 end
949 end
950
950
951 # Get issues sorted by root_id, lft so that parent issues
951 # Get issues sorted by root_id, lft so that parent issues
952 # get copied before their children
952 # get copied before their children
953 project.issues.reorder('root_id, lft').each do |issue|
953 project.issues.reorder('root_id, lft').each do |issue|
954 new_issue = Issue.new
954 new_issue = Issue.new
955 new_issue.copy_from(issue, :subtasks => false, :link => false)
955 new_issue.copy_from(issue, :subtasks => false, :link => false)
956 new_issue.project = self
956 new_issue.project = self
957 # Changing project resets the custom field values
957 # Changing project resets the custom field values
958 # TODO: handle this in Issue#project=
958 # TODO: handle this in Issue#project=
959 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
959 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
960 # Reassign fixed_versions by name, since names are unique per project
960 # Reassign fixed_versions by name, since names are unique per project
961 if issue.fixed_version && issue.fixed_version.project == project
961 if issue.fixed_version && issue.fixed_version.project == project
962 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
962 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
963 end
963 end
964 # Reassign version custom field values
964 # Reassign version custom field values
965 new_issue.custom_field_values.each do |custom_value|
965 new_issue.custom_field_values.each do |custom_value|
966 if custom_value.custom_field.field_format == 'version' && custom_value.value.present?
966 if custom_value.custom_field.field_format == 'version' && custom_value.value.present?
967 versions = Version.where(:id => custom_value.value).to_a
967 versions = Version.where(:id => custom_value.value).to_a
968 new_value = versions.map do |version|
968 new_value = versions.map do |version|
969 if version.project == project
969 if version.project == project
970 self.versions.detect {|v| v.name == version.name}.try(:id)
970 self.versions.detect {|v| v.name == version.name}.try(:id)
971 else
971 else
972 version.id
972 version.id
973 end
973 end
974 end
974 end
975 new_value.compact!
975 new_value.compact!
976 new_value = new_value.first unless custom_value.custom_field.multiple?
976 new_value = new_value.first unless custom_value.custom_field.multiple?
977 custom_value.value = new_value
977 custom_value.value = new_value
978 end
978 end
979 end
979 end
980 # Reassign the category by name, since names are unique per project
980 # Reassign the category by name, since names are unique per project
981 if issue.category
981 if issue.category
982 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
982 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
983 end
983 end
984 # Parent issue
984 # Parent issue
985 if issue.parent_id
985 if issue.parent_id
986 if copied_parent = issues_map[issue.parent_id]
986 if copied_parent = issues_map[issue.parent_id]
987 new_issue.parent_issue_id = copied_parent.id
987 new_issue.parent_issue_id = copied_parent.id
988 end
988 end
989 end
989 end
990
990
991 self.issues << new_issue
991 self.issues << new_issue
992 if new_issue.new_record?
992 if new_issue.new_record?
993 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
993 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
994 else
994 else
995 issues_map[issue.id] = new_issue unless new_issue.new_record?
995 issues_map[issue.id] = new_issue unless new_issue.new_record?
996 end
996 end
997 end
997 end
998
998
999 # Restore locked/closed version statuses
999 # Restore locked/closed version statuses
1000 version_statuses.each do |version, status|
1000 version_statuses.each do |version, status|
1001 version.update_attribute :status, status
1001 version.update_attribute :status, status
1002 end
1002 end
1003
1003
1004 # Relations after in case issues related each other
1004 # Relations after in case issues related each other
1005 project.issues.each do |issue|
1005 project.issues.each do |issue|
1006 new_issue = issues_map[issue.id]
1006 new_issue = issues_map[issue.id]
1007 unless new_issue
1007 unless new_issue
1008 # Issue was not copied
1008 # Issue was not copied
1009 next
1009 next
1010 end
1010 end
1011
1011
1012 # Relations
1012 # Relations
1013 issue.relations_from.each do |source_relation|
1013 issue.relations_from.each do |source_relation|
1014 new_issue_relation = IssueRelation.new
1014 new_issue_relation = IssueRelation.new
1015 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
1015 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
1016 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
1016 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
1017 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
1017 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
1018 new_issue_relation.issue_to = source_relation.issue_to
1018 new_issue_relation.issue_to = source_relation.issue_to
1019 end
1019 end
1020 new_issue.relations_from << new_issue_relation
1020 new_issue.relations_from << new_issue_relation
1021 end
1021 end
1022
1022
1023 issue.relations_to.each do |source_relation|
1023 issue.relations_to.each do |source_relation|
1024 new_issue_relation = IssueRelation.new
1024 new_issue_relation = IssueRelation.new
1025 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
1025 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
1026 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
1026 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
1027 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
1027 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
1028 new_issue_relation.issue_from = source_relation.issue_from
1028 new_issue_relation.issue_from = source_relation.issue_from
1029 end
1029 end
1030 new_issue.relations_to << new_issue_relation
1030 new_issue.relations_to << new_issue_relation
1031 end
1031 end
1032 end
1032 end
1033 end
1033 end
1034
1034
1035 # Copies members from +project+
1035 # Copies members from +project+
1036 def copy_members(project)
1036 def copy_members(project)
1037 # Copy users first, then groups to handle members with inherited and given roles
1037 # Copy users first, then groups to handle members with inherited and given roles
1038 members_to_copy = []
1038 members_to_copy = []
1039 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
1039 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
1040 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
1040 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
1041
1041
1042 members_to_copy.each do |member|
1042 members_to_copy.each do |member|
1043 new_member = Member.new
1043 new_member = Member.new
1044 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
1044 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
1045 # only copy non inherited roles
1045 # only copy non inherited roles
1046 # inherited roles will be added when copying the group membership
1046 # inherited roles will be added when copying the group membership
1047 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
1047 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
1048 next if role_ids.empty?
1048 next if role_ids.empty?
1049 new_member.role_ids = role_ids
1049 new_member.role_ids = role_ids
1050 new_member.project = self
1050 new_member.project = self
1051 self.members << new_member
1051 self.members << new_member
1052 end
1052 end
1053 end
1053 end
1054
1054
1055 # Copies queries from +project+
1055 # Copies queries from +project+
1056 def copy_queries(project)
1056 def copy_queries(project)
1057 project.queries.each do |query|
1057 project.queries.each do |query|
1058 new_query = query.class.new
1058 new_query = query.class.new
1059 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
1059 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
1060 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
1060 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
1061 new_query.project = self
1061 new_query.project = self
1062 new_query.user_id = query.user_id
1062 new_query.user_id = query.user_id
1063 new_query.role_ids = query.role_ids if query.visibility == ::Query::VISIBILITY_ROLES
1063 new_query.role_ids = query.role_ids if query.visibility == ::Query::VISIBILITY_ROLES
1064 self.queries << new_query
1064 self.queries << new_query
1065 end
1065 end
1066 end
1066 end
1067
1067
1068 # Copies boards from +project+
1068 # Copies boards from +project+
1069 def copy_boards(project)
1069 def copy_boards(project)
1070 project.boards.each do |board|
1070 project.boards.each do |board|
1071 new_board = Board.new
1071 new_board = Board.new
1072 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
1072 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
1073 new_board.project = self
1073 new_board.project = self
1074 self.boards << new_board
1074 self.boards << new_board
1075 end
1075 end
1076 end
1076 end
1077
1077
1078 def allowed_permissions
1078 def allowed_permissions
1079 @allowed_permissions ||= begin
1079 @allowed_permissions ||= begin
1080 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
1080 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
1081 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
1081 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
1082 end
1082 end
1083 end
1083 end
1084
1084
1085 def allowed_actions
1085 def allowed_actions
1086 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
1086 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
1087 end
1087 end
1088
1088
1089 # Archives subprojects recursively
1089 # Archives subprojects recursively
1090 def archive!
1090 def archive!
1091 children.each do |subproject|
1091 children.each do |subproject|
1092 subproject.send :archive!
1092 subproject.send :archive!
1093 end
1093 end
1094 update_attribute :status, STATUS_ARCHIVED
1094 update_attribute :status, STATUS_ARCHIVED
1095 end
1095 end
1096 end
1096 end
General Comments 0
You need to be logged in to leave comments. Login now