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