##// END OF EJS Templates
Revert "Fixed: gantt broken when no due date on project issues and versions."...
Eric Davis -
r4070:83bfb8cd2b0a
parent child
Show More
@@ -1,775 +1,774
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities
24 has_many :time_entry_activities
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
26 has_many :memberships, :class_name => 'Member'
26 has_many :memberships, :class_name => 'Member'
27 has_many :member_principals, :class_name => 'Member',
27 has_many :member_principals, :class_name => 'Member',
28 :include => :principal,
28 :include => :principal,
29 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
29 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
30 has_many :users, :through => :members
30 has_many :users, :through => :members
31 has_many :principals, :through => :member_principals, :source => :principal
31 has_many :principals, :through => :member_principals, :source => :principal
32
32
33 has_many :enabled_modules, :dependent => :delete_all
33 has_many :enabled_modules, :dependent => :delete_all
34 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
34 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
35 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
35 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
36 has_many :issue_changes, :through => :issues, :source => :journals
36 has_many :issue_changes, :through => :issues, :source => :journals
37 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
37 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
38 has_many :time_entries, :dependent => :delete_all
38 has_many :time_entries, :dependent => :delete_all
39 has_many :queries, :dependent => :delete_all
39 has_many :queries, :dependent => :delete_all
40 has_many :documents, :dependent => :destroy
40 has_many :documents, :dependent => :destroy
41 has_many :news, :dependent => :delete_all, :include => :author
41 has_many :news, :dependent => :delete_all, :include => :author
42 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
42 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
43 has_many :boards, :dependent => :destroy, :order => "position ASC"
43 has_many :boards, :dependent => :destroy, :order => "position ASC"
44 has_one :repository, :dependent => :destroy
44 has_one :repository, :dependent => :destroy
45 has_many :changesets, :through => :repository
45 has_many :changesets, :through => :repository
46 has_one :wiki, :dependent => :destroy
46 has_one :wiki, :dependent => :destroy
47 # Custom field for the project issues
47 # Custom field for the project issues
48 has_and_belongs_to_many :issue_custom_fields,
48 has_and_belongs_to_many :issue_custom_fields,
49 :class_name => 'IssueCustomField',
49 :class_name => 'IssueCustomField',
50 :order => "#{CustomField.table_name}.position",
50 :order => "#{CustomField.table_name}.position",
51 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
51 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
52 :association_foreign_key => 'custom_field_id'
52 :association_foreign_key => 'custom_field_id'
53
53
54 acts_as_nested_set :order => 'name'
54 acts_as_nested_set :order => 'name'
55 acts_as_attachable :view_permission => :view_files,
55 acts_as_attachable :view_permission => :view_files,
56 :delete_permission => :manage_files
56 :delete_permission => :manage_files
57
57
58 acts_as_customizable
58 acts_as_customizable
59 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
59 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
60 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
60 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
61 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
61 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
62 :author => nil
62 :author => nil
63
63
64 attr_protected :status, :enabled_module_names
64 attr_protected :status, :enabled_module_names
65
65
66 validates_presence_of :name, :identifier
66 validates_presence_of :name, :identifier
67 validates_uniqueness_of :name, :identifier
67 validates_uniqueness_of :name, :identifier
68 validates_associated :repository, :wiki
68 validates_associated :repository, :wiki
69 validates_length_of :name, :maximum => 30
69 validates_length_of :name, :maximum => 30
70 validates_length_of :homepage, :maximum => 255
70 validates_length_of :homepage, :maximum => 255
71 validates_length_of :identifier, :in => 1..20
71 validates_length_of :identifier, :in => 1..20
72 # donwcase letters, digits, dashes but not digits only
72 # donwcase letters, digits, dashes but not digits only
73 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
73 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
74 # reserved words
74 # reserved words
75 validates_exclusion_of :identifier, :in => %w( new )
75 validates_exclusion_of :identifier, :in => %w( new )
76
76
77 before_destroy :delete_all_members, :destroy_children
77 before_destroy :delete_all_members, :destroy_children
78
78
79 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
79 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
80 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
80 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
81 named_scope :all_public, { :conditions => { :is_public => true } }
81 named_scope :all_public, { :conditions => { :is_public => true } }
82 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
82 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
83
83
84 def identifier=(identifier)
84 def identifier=(identifier)
85 super unless identifier_frozen?
85 super unless identifier_frozen?
86 end
86 end
87
87
88 def identifier_frozen?
88 def identifier_frozen?
89 errors[:identifier].nil? && !(new_record? || identifier.blank?)
89 errors[:identifier].nil? && !(new_record? || identifier.blank?)
90 end
90 end
91
91
92 # returns latest created projects
92 # returns latest created projects
93 # non public projects will be returned only if user is a member of those
93 # non public projects will be returned only if user is a member of those
94 def self.latest(user=nil, count=5)
94 def self.latest(user=nil, count=5)
95 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
95 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
96 end
96 end
97
97
98 # Returns a SQL :conditions string used to find all active projects for the specified user.
98 # Returns a SQL :conditions string used to find all active projects for the specified user.
99 #
99 #
100 # Examples:
100 # Examples:
101 # Projects.visible_by(admin) => "projects.status = 1"
101 # Projects.visible_by(admin) => "projects.status = 1"
102 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
102 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
103 def self.visible_by(user=nil)
103 def self.visible_by(user=nil)
104 user ||= User.current
104 user ||= User.current
105 if user && user.admin?
105 if user && user.admin?
106 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
106 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
107 elsif user && user.memberships.any?
107 elsif user && user.memberships.any?
108 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
108 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
109 else
109 else
110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
111 end
111 end
112 end
112 end
113
113
114 def self.allowed_to_condition(user, permission, options={})
114 def self.allowed_to_condition(user, permission, options={})
115 statements = []
115 statements = []
116 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
116 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
117 if perm = Redmine::AccessControl.permission(permission)
117 if perm = Redmine::AccessControl.permission(permission)
118 unless perm.project_module.nil?
118 unless perm.project_module.nil?
119 # If the permission belongs to a project module, make sure the module is enabled
119 # If the permission belongs to a project module, make sure the module is enabled
120 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
120 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
121 end
121 end
122 end
122 end
123 if options[:project]
123 if options[:project]
124 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
124 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
125 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
125 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
126 base_statement = "(#{project_statement}) AND (#{base_statement})"
126 base_statement = "(#{project_statement}) AND (#{base_statement})"
127 end
127 end
128 if user.admin?
128 if user.admin?
129 # no restriction
129 # no restriction
130 else
130 else
131 statements << "1=0"
131 statements << "1=0"
132 if user.logged?
132 if user.logged?
133 if Role.non_member.allowed_to?(permission) && !options[:member]
133 if Role.non_member.allowed_to?(permission) && !options[:member]
134 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
134 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
135 end
135 end
136 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
136 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
137 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
137 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
138 else
138 else
139 if Role.anonymous.allowed_to?(permission) && !options[:member]
139 if Role.anonymous.allowed_to?(permission) && !options[:member]
140 # anonymous user allowed on public project
140 # anonymous user allowed on public project
141 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
141 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
142 end
142 end
143 end
143 end
144 end
144 end
145 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
145 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
146 end
146 end
147
147
148 # Returns the Systemwide and project specific activities
148 # Returns the Systemwide and project specific activities
149 def activities(include_inactive=false)
149 def activities(include_inactive=false)
150 if include_inactive
150 if include_inactive
151 return all_activities
151 return all_activities
152 else
152 else
153 return active_activities
153 return active_activities
154 end
154 end
155 end
155 end
156
156
157 # Will create a new Project specific Activity or update an existing one
157 # Will create a new Project specific Activity or update an existing one
158 #
158 #
159 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
159 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
160 # does not successfully save.
160 # does not successfully save.
161 def update_or_create_time_entry_activity(id, activity_hash)
161 def update_or_create_time_entry_activity(id, activity_hash)
162 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
162 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
163 self.create_time_entry_activity_if_needed(activity_hash)
163 self.create_time_entry_activity_if_needed(activity_hash)
164 else
164 else
165 activity = project.time_entry_activities.find_by_id(id.to_i)
165 activity = project.time_entry_activities.find_by_id(id.to_i)
166 activity.update_attributes(activity_hash) if activity
166 activity.update_attributes(activity_hash) if activity
167 end
167 end
168 end
168 end
169
169
170 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
170 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
171 #
171 #
172 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
172 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
173 # does not successfully save.
173 # does not successfully save.
174 def create_time_entry_activity_if_needed(activity)
174 def create_time_entry_activity_if_needed(activity)
175 if activity['parent_id']
175 if activity['parent_id']
176
176
177 parent_activity = TimeEntryActivity.find(activity['parent_id'])
177 parent_activity = TimeEntryActivity.find(activity['parent_id'])
178 activity['name'] = parent_activity.name
178 activity['name'] = parent_activity.name
179 activity['position'] = parent_activity.position
179 activity['position'] = parent_activity.position
180
180
181 if Enumeration.overridding_change?(activity, parent_activity)
181 if Enumeration.overridding_change?(activity, parent_activity)
182 project_activity = self.time_entry_activities.create(activity)
182 project_activity = self.time_entry_activities.create(activity)
183
183
184 if project_activity.new_record?
184 if project_activity.new_record?
185 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
185 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
186 else
186 else
187 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
187 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
188 end
188 end
189 end
189 end
190 end
190 end
191 end
191 end
192
192
193 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
193 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
194 #
194 #
195 # Examples:
195 # Examples:
196 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
196 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
197 # project.project_condition(false) => "projects.id = 1"
197 # project.project_condition(false) => "projects.id = 1"
198 def project_condition(with_subprojects)
198 def project_condition(with_subprojects)
199 cond = "#{Project.table_name}.id = #{id}"
199 cond = "#{Project.table_name}.id = #{id}"
200 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
200 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
201 cond
201 cond
202 end
202 end
203
203
204 def self.find(*args)
204 def self.find(*args)
205 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
205 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
206 project = find_by_identifier(*args)
206 project = find_by_identifier(*args)
207 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
207 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
208 project
208 project
209 else
209 else
210 super
210 super
211 end
211 end
212 end
212 end
213
213
214 def to_param
214 def to_param
215 # id is used for projects with a numeric identifier (compatibility)
215 # id is used for projects with a numeric identifier (compatibility)
216 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
216 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
217 end
217 end
218
218
219 def active?
219 def active?
220 self.status == STATUS_ACTIVE
220 self.status == STATUS_ACTIVE
221 end
221 end
222
222
223 # Archives the project and its descendants
223 # Archives the project and its descendants
224 def archive
224 def archive
225 # Check that there is no issue of a non descendant project that is assigned
225 # Check that there is no issue of a non descendant project that is assigned
226 # to one of the project or descendant versions
226 # to one of the project or descendant versions
227 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
227 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
228 if v_ids.any? && Issue.find(:first, :include => :project,
228 if v_ids.any? && Issue.find(:first, :include => :project,
229 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
229 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
230 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
230 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
231 return false
231 return false
232 end
232 end
233 Project.transaction do
233 Project.transaction do
234 archive!
234 archive!
235 end
235 end
236 true
236 true
237 end
237 end
238
238
239 # Unarchives the project
239 # Unarchives the project
240 # All its ancestors must be active
240 # All its ancestors must be active
241 def unarchive
241 def unarchive
242 return false if ancestors.detect {|a| !a.active?}
242 return false if ancestors.detect {|a| !a.active?}
243 update_attribute :status, STATUS_ACTIVE
243 update_attribute :status, STATUS_ACTIVE
244 end
244 end
245
245
246 # Returns an array of projects the project can be moved to
246 # Returns an array of projects the project can be moved to
247 # by the current user
247 # by the current user
248 def allowed_parents
248 def allowed_parents
249 return @allowed_parents if @allowed_parents
249 return @allowed_parents if @allowed_parents
250 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
250 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
251 @allowed_parents = @allowed_parents - self_and_descendants
251 @allowed_parents = @allowed_parents - self_and_descendants
252 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
252 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
253 @allowed_parents << nil
253 @allowed_parents << nil
254 end
254 end
255 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
255 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
256 @allowed_parents << parent
256 @allowed_parents << parent
257 end
257 end
258 @allowed_parents
258 @allowed_parents
259 end
259 end
260
260
261 # Sets the parent of the project with authorization check
261 # Sets the parent of the project with authorization check
262 def set_allowed_parent!(p)
262 def set_allowed_parent!(p)
263 unless p.nil? || p.is_a?(Project)
263 unless p.nil? || p.is_a?(Project)
264 if p.to_s.blank?
264 if p.to_s.blank?
265 p = nil
265 p = nil
266 else
266 else
267 p = Project.find_by_id(p)
267 p = Project.find_by_id(p)
268 return false unless p
268 return false unless p
269 end
269 end
270 end
270 end
271 if p.nil?
271 if p.nil?
272 if !new_record? && allowed_parents.empty?
272 if !new_record? && allowed_parents.empty?
273 return false
273 return false
274 end
274 end
275 elsif !allowed_parents.include?(p)
275 elsif !allowed_parents.include?(p)
276 return false
276 return false
277 end
277 end
278 set_parent!(p)
278 set_parent!(p)
279 end
279 end
280
280
281 # Sets the parent of the project
281 # Sets the parent of the project
282 # Argument can be either a Project, a String, a Fixnum or nil
282 # Argument can be either a Project, a String, a Fixnum or nil
283 def set_parent!(p)
283 def set_parent!(p)
284 unless p.nil? || p.is_a?(Project)
284 unless p.nil? || p.is_a?(Project)
285 if p.to_s.blank?
285 if p.to_s.blank?
286 p = nil
286 p = nil
287 else
287 else
288 p = Project.find_by_id(p)
288 p = Project.find_by_id(p)
289 return false unless p
289 return false unless p
290 end
290 end
291 end
291 end
292 if p == parent && !p.nil?
292 if p == parent && !p.nil?
293 # Nothing to do
293 # Nothing to do
294 true
294 true
295 elsif p.nil? || (p.active? && move_possible?(p))
295 elsif p.nil? || (p.active? && move_possible?(p))
296 # Insert the project so that target's children or root projects stay alphabetically sorted
296 # Insert the project so that target's children or root projects stay alphabetically sorted
297 sibs = (p.nil? ? self.class.roots : p.children)
297 sibs = (p.nil? ? self.class.roots : p.children)
298 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
298 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
299 if to_be_inserted_before
299 if to_be_inserted_before
300 move_to_left_of(to_be_inserted_before)
300 move_to_left_of(to_be_inserted_before)
301 elsif p.nil?
301 elsif p.nil?
302 if sibs.empty?
302 if sibs.empty?
303 # move_to_root adds the project in first (ie. left) position
303 # move_to_root adds the project in first (ie. left) position
304 move_to_root
304 move_to_root
305 else
305 else
306 move_to_right_of(sibs.last) unless self == sibs.last
306 move_to_right_of(sibs.last) unless self == sibs.last
307 end
307 end
308 else
308 else
309 # move_to_child_of adds the project in last (ie.right) position
309 # move_to_child_of adds the project in last (ie.right) position
310 move_to_child_of(p)
310 move_to_child_of(p)
311 end
311 end
312 Issue.update_versions_from_hierarchy_change(self)
312 Issue.update_versions_from_hierarchy_change(self)
313 true
313 true
314 else
314 else
315 # Can not move to the given target
315 # Can not move to the given target
316 false
316 false
317 end
317 end
318 end
318 end
319
319
320 # Returns an array of the trackers used by the project and its active sub projects
320 # Returns an array of the trackers used by the project and its active sub projects
321 def rolled_up_trackers
321 def rolled_up_trackers
322 @rolled_up_trackers ||=
322 @rolled_up_trackers ||=
323 Tracker.find(:all, :include => :projects,
323 Tracker.find(:all, :include => :projects,
324 :select => "DISTINCT #{Tracker.table_name}.*",
324 :select => "DISTINCT #{Tracker.table_name}.*",
325 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
325 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
326 :order => "#{Tracker.table_name}.position")
326 :order => "#{Tracker.table_name}.position")
327 end
327 end
328
328
329 # Closes open and locked project versions that are completed
329 # Closes open and locked project versions that are completed
330 def close_completed_versions
330 def close_completed_versions
331 Version.transaction do
331 Version.transaction do
332 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
332 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
333 if version.completed?
333 if version.completed?
334 version.update_attribute(:status, 'closed')
334 version.update_attribute(:status, 'closed')
335 end
335 end
336 end
336 end
337 end
337 end
338 end
338 end
339
339
340 # Returns a scope of the Versions on subprojects
340 # Returns a scope of the Versions on subprojects
341 def rolled_up_versions
341 def rolled_up_versions
342 @rolled_up_versions ||=
342 @rolled_up_versions ||=
343 Version.scoped(:include => :project,
343 Version.scoped(:include => :project,
344 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
344 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
345 end
345 end
346
346
347 # Returns a scope of the Versions used by the project
347 # Returns a scope of the Versions used by the project
348 def shared_versions
348 def shared_versions
349 @shared_versions ||=
349 @shared_versions ||=
350 Version.scoped(:include => :project,
350 Version.scoped(:include => :project,
351 :conditions => "#{Project.table_name}.id = #{id}" +
351 :conditions => "#{Project.table_name}.id = #{id}" +
352 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
352 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
353 " #{Version.table_name}.sharing = 'system'" +
353 " #{Version.table_name}.sharing = 'system'" +
354 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
354 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
355 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
355 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
356 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
356 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
357 "))")
357 "))")
358 end
358 end
359
359
360 # Returns a hash of project users grouped by role
360 # Returns a hash of project users grouped by role
361 def users_by_role
361 def users_by_role
362 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
362 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
363 m.roles.each do |r|
363 m.roles.each do |r|
364 h[r] ||= []
364 h[r] ||= []
365 h[r] << m.user
365 h[r] << m.user
366 end
366 end
367 h
367 h
368 end
368 end
369 end
369 end
370
370
371 # Deletes all project's members
371 # Deletes all project's members
372 def delete_all_members
372 def delete_all_members
373 me, mr = Member.table_name, MemberRole.table_name
373 me, mr = Member.table_name, MemberRole.table_name
374 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
374 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
375 Member.delete_all(['project_id = ?', id])
375 Member.delete_all(['project_id = ?', id])
376 end
376 end
377
377
378 # Users issues can be assigned to
378 # Users issues can be assigned to
379 def assignable_users
379 def assignable_users
380 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
380 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
381 end
381 end
382
382
383 # Returns the mail adresses of users that should be always notified on project events
383 # Returns the mail adresses of users that should be always notified on project events
384 def recipients
384 def recipients
385 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
385 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
386 end
386 end
387
387
388 # Returns the users that should be notified on project events
388 # Returns the users that should be notified on project events
389 def notified_users
389 def notified_users
390 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
390 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
391 end
391 end
392
392
393 # Returns an array of all custom fields enabled for project issues
393 # Returns an array of all custom fields enabled for project issues
394 # (explictly associated custom fields and custom fields enabled for all projects)
394 # (explictly associated custom fields and custom fields enabled for all projects)
395 def all_issue_custom_fields
395 def all_issue_custom_fields
396 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
396 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
397 end
397 end
398
398
399 def project
399 def project
400 self
400 self
401 end
401 end
402
402
403 def <=>(project)
403 def <=>(project)
404 name.downcase <=> project.name.downcase
404 name.downcase <=> project.name.downcase
405 end
405 end
406
406
407 def to_s
407 def to_s
408 name
408 name
409 end
409 end
410
410
411 # Returns a short description of the projects (first lines)
411 # Returns a short description of the projects (first lines)
412 def short_description(length = 255)
412 def short_description(length = 255)
413 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
413 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
414 end
414 end
415
415
416 def css_classes
416 def css_classes
417 s = 'project'
417 s = 'project'
418 s << ' root' if root?
418 s << ' root' if root?
419 s << ' child' if child?
419 s << ' child' if child?
420 s << (leaf? ? ' leaf' : ' parent')
420 s << (leaf? ? ' leaf' : ' parent')
421 s
421 s
422 end
422 end
423
423
424 # The earliest start date of a project, based on it's issues and versions
424 # The earliest start date of a project, based on it's issues and versions
425 def start_date
425 def start_date
426 if module_enabled?(:issue_tracking)
426 if module_enabled?(:issue_tracking)
427 [
427 [
428 issues.minimum('start_date'),
428 issues.minimum('start_date'),
429 shared_versions.collect(&:effective_date),
429 shared_versions.collect(&:effective_date),
430 shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
430 shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
431 ].flatten.compact.min
431 ].flatten.compact.min
432 end
432 end
433 end
433 end
434
434
435 # The latest due date of an issue or version, otherwise today
435 # The latest due date of an issue or version
436 def due_date
436 def due_date
437 if module_enabled?(:issue_tracking)
437 if module_enabled?(:issue_tracking)
438 [
438 [
439 issues.maximum('due_date'),
439 issues.maximum('due_date'),
440 shared_versions.collect(&:effective_date),
440 shared_versions.collect(&:effective_date),
441 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')},
441 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
442 Date.today
443 ].flatten.compact.max
442 ].flatten.compact.max
444 end
443 end
445 end
444 end
446
445
447 def overdue?
446 def overdue?
448 active? && !due_date.nil? && (due_date < Date.today)
447 active? && !due_date.nil? && (due_date < Date.today)
449 end
448 end
450
449
451 # Returns the percent completed for this project, based on the
450 # Returns the percent completed for this project, based on the
452 # progress on it's versions.
451 # progress on it's versions.
453 def completed_percent(options={:include_subprojects => false})
452 def completed_percent(options={:include_subprojects => false})
454 if options.delete(:include_subprojects)
453 if options.delete(:include_subprojects)
455 total = self_and_descendants.collect(&:completed_percent).sum
454 total = self_and_descendants.collect(&:completed_percent).sum
456
455
457 total / self_and_descendants.count
456 total / self_and_descendants.count
458 else
457 else
459 if versions.count > 0
458 if versions.count > 0
460 total = versions.collect(&:completed_pourcent).sum
459 total = versions.collect(&:completed_pourcent).sum
461
460
462 total / versions.count
461 total / versions.count
463 else
462 else
464 100
463 100
465 end
464 end
466 end
465 end
467 end
466 end
468
467
469 # Return true if this project is allowed to do the specified action.
468 # Return true if this project is allowed to do the specified action.
470 # action can be:
469 # action can be:
471 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
470 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
472 # * a permission Symbol (eg. :edit_project)
471 # * a permission Symbol (eg. :edit_project)
473 def allows_to?(action)
472 def allows_to?(action)
474 if action.is_a? Hash
473 if action.is_a? Hash
475 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
474 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
476 else
475 else
477 allowed_permissions.include? action
476 allowed_permissions.include? action
478 end
477 end
479 end
478 end
480
479
481 def module_enabled?(module_name)
480 def module_enabled?(module_name)
482 module_name = module_name.to_s
481 module_name = module_name.to_s
483 enabled_modules.detect {|m| m.name == module_name}
482 enabled_modules.detect {|m| m.name == module_name}
484 end
483 end
485
484
486 def enabled_module_names=(module_names)
485 def enabled_module_names=(module_names)
487 if module_names && module_names.is_a?(Array)
486 if module_names && module_names.is_a?(Array)
488 module_names = module_names.collect(&:to_s)
487 module_names = module_names.collect(&:to_s)
489 # remove disabled modules
488 # remove disabled modules
490 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
489 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
491 # add new modules
490 # add new modules
492 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
491 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
493 else
492 else
494 enabled_modules.clear
493 enabled_modules.clear
495 end
494 end
496 end
495 end
497
496
498 # Returns an array of projects that are in this project's hierarchy
497 # Returns an array of projects that are in this project's hierarchy
499 #
498 #
500 # Example: parents, children, siblings
499 # Example: parents, children, siblings
501 def hierarchy
500 def hierarchy
502 parents = project.self_and_ancestors || []
501 parents = project.self_and_ancestors || []
503 descendants = project.descendants || []
502 descendants = project.descendants || []
504 project_hierarchy = parents | descendants # Set union
503 project_hierarchy = parents | descendants # Set union
505 end
504 end
506
505
507 # Returns an auto-generated project identifier based on the last identifier used
506 # Returns an auto-generated project identifier based on the last identifier used
508 def self.next_identifier
507 def self.next_identifier
509 p = Project.find(:first, :order => 'created_on DESC')
508 p = Project.find(:first, :order => 'created_on DESC')
510 p.nil? ? nil : p.identifier.to_s.succ
509 p.nil? ? nil : p.identifier.to_s.succ
511 end
510 end
512
511
513 # Copies and saves the Project instance based on the +project+.
512 # Copies and saves the Project instance based on the +project+.
514 # Duplicates the source project's:
513 # Duplicates the source project's:
515 # * Wiki
514 # * Wiki
516 # * Versions
515 # * Versions
517 # * Categories
516 # * Categories
518 # * Issues
517 # * Issues
519 # * Members
518 # * Members
520 # * Queries
519 # * Queries
521 #
520 #
522 # Accepts an +options+ argument to specify what to copy
521 # Accepts an +options+ argument to specify what to copy
523 #
522 #
524 # Examples:
523 # Examples:
525 # project.copy(1) # => copies everything
524 # project.copy(1) # => copies everything
526 # project.copy(1, :only => 'members') # => copies members only
525 # project.copy(1, :only => 'members') # => copies members only
527 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
526 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
528 def copy(project, options={})
527 def copy(project, options={})
529 project = project.is_a?(Project) ? project : Project.find(project)
528 project = project.is_a?(Project) ? project : Project.find(project)
530
529
531 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
530 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
532 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
531 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
533
532
534 Project.transaction do
533 Project.transaction do
535 if save
534 if save
536 reload
535 reload
537 to_be_copied.each do |name|
536 to_be_copied.each do |name|
538 send "copy_#{name}", project
537 send "copy_#{name}", project
539 end
538 end
540 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
539 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
541 save
540 save
542 end
541 end
543 end
542 end
544 end
543 end
545
544
546
545
547 # Copies +project+ and returns the new instance. This will not save
546 # Copies +project+ and returns the new instance. This will not save
548 # the copy
547 # the copy
549 def self.copy_from(project)
548 def self.copy_from(project)
550 begin
549 begin
551 project = project.is_a?(Project) ? project : Project.find(project)
550 project = project.is_a?(Project) ? project : Project.find(project)
552 if project
551 if project
553 # clear unique attributes
552 # clear unique attributes
554 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
553 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
555 copy = Project.new(attributes)
554 copy = Project.new(attributes)
556 copy.enabled_modules = project.enabled_modules
555 copy.enabled_modules = project.enabled_modules
557 copy.trackers = project.trackers
556 copy.trackers = project.trackers
558 copy.custom_values = project.custom_values.collect {|v| v.clone}
557 copy.custom_values = project.custom_values.collect {|v| v.clone}
559 copy.issue_custom_fields = project.issue_custom_fields
558 copy.issue_custom_fields = project.issue_custom_fields
560 return copy
559 return copy
561 else
560 else
562 return nil
561 return nil
563 end
562 end
564 rescue ActiveRecord::RecordNotFound
563 rescue ActiveRecord::RecordNotFound
565 return nil
564 return nil
566 end
565 end
567 end
566 end
568
567
569 private
568 private
570
569
571 # Destroys children before destroying self
570 # Destroys children before destroying self
572 def destroy_children
571 def destroy_children
573 children.each do |child|
572 children.each do |child|
574 child.destroy
573 child.destroy
575 end
574 end
576 end
575 end
577
576
578 # Copies wiki from +project+
577 # Copies wiki from +project+
579 def copy_wiki(project)
578 def copy_wiki(project)
580 # Check that the source project has a wiki first
579 # Check that the source project has a wiki first
581 unless project.wiki.nil?
580 unless project.wiki.nil?
582 self.wiki ||= Wiki.new
581 self.wiki ||= Wiki.new
583 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
582 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
584 wiki_pages_map = {}
583 wiki_pages_map = {}
585 project.wiki.pages.each do |page|
584 project.wiki.pages.each do |page|
586 # Skip pages without content
585 # Skip pages without content
587 next if page.content.nil?
586 next if page.content.nil?
588 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
587 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
589 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
588 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
590 new_wiki_page.content = new_wiki_content
589 new_wiki_page.content = new_wiki_content
591 wiki.pages << new_wiki_page
590 wiki.pages << new_wiki_page
592 wiki_pages_map[page.id] = new_wiki_page
591 wiki_pages_map[page.id] = new_wiki_page
593 end
592 end
594 wiki.save
593 wiki.save
595 # Reproduce page hierarchy
594 # Reproduce page hierarchy
596 project.wiki.pages.each do |page|
595 project.wiki.pages.each do |page|
597 if page.parent_id && wiki_pages_map[page.id]
596 if page.parent_id && wiki_pages_map[page.id]
598 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
597 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
599 wiki_pages_map[page.id].save
598 wiki_pages_map[page.id].save
600 end
599 end
601 end
600 end
602 end
601 end
603 end
602 end
604
603
605 # Copies versions from +project+
604 # Copies versions from +project+
606 def copy_versions(project)
605 def copy_versions(project)
607 project.versions.each do |version|
606 project.versions.each do |version|
608 new_version = Version.new
607 new_version = Version.new
609 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
608 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
610 self.versions << new_version
609 self.versions << new_version
611 end
610 end
612 end
611 end
613
612
614 # Copies issue categories from +project+
613 # Copies issue categories from +project+
615 def copy_issue_categories(project)
614 def copy_issue_categories(project)
616 project.issue_categories.each do |issue_category|
615 project.issue_categories.each do |issue_category|
617 new_issue_category = IssueCategory.new
616 new_issue_category = IssueCategory.new
618 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
617 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
619 self.issue_categories << new_issue_category
618 self.issue_categories << new_issue_category
620 end
619 end
621 end
620 end
622
621
623 # Copies issues from +project+
622 # Copies issues from +project+
624 def copy_issues(project)
623 def copy_issues(project)
625 # Stores the source issue id as a key and the copied issues as the
624 # Stores the source issue id as a key and the copied issues as the
626 # value. Used to map the two togeather for issue relations.
625 # value. Used to map the two togeather for issue relations.
627 issues_map = {}
626 issues_map = {}
628
627
629 # Get issues sorted by root_id, lft so that parent issues
628 # Get issues sorted by root_id, lft so that parent issues
630 # get copied before their children
629 # get copied before their children
631 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
630 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
632 new_issue = Issue.new
631 new_issue = Issue.new
633 new_issue.copy_from(issue)
632 new_issue.copy_from(issue)
634 new_issue.project = self
633 new_issue.project = self
635 # Reassign fixed_versions by name, since names are unique per
634 # Reassign fixed_versions by name, since names are unique per
636 # project and the versions for self are not yet saved
635 # project and the versions for self are not yet saved
637 if issue.fixed_version
636 if issue.fixed_version
638 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
637 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
639 end
638 end
640 # Reassign the category by name, since names are unique per
639 # Reassign the category by name, since names are unique per
641 # project and the categories for self are not yet saved
640 # project and the categories for self are not yet saved
642 if issue.category
641 if issue.category
643 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
642 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
644 end
643 end
645 # Parent issue
644 # Parent issue
646 if issue.parent_id
645 if issue.parent_id
647 if copied_parent = issues_map[issue.parent_id]
646 if copied_parent = issues_map[issue.parent_id]
648 new_issue.parent_issue_id = copied_parent.id
647 new_issue.parent_issue_id = copied_parent.id
649 end
648 end
650 end
649 end
651
650
652 self.issues << new_issue
651 self.issues << new_issue
653 issues_map[issue.id] = new_issue
652 issues_map[issue.id] = new_issue
654 end
653 end
655
654
656 # Relations after in case issues related each other
655 # Relations after in case issues related each other
657 project.issues.each do |issue|
656 project.issues.each do |issue|
658 new_issue = issues_map[issue.id]
657 new_issue = issues_map[issue.id]
659
658
660 # Relations
659 # Relations
661 issue.relations_from.each do |source_relation|
660 issue.relations_from.each do |source_relation|
662 new_issue_relation = IssueRelation.new
661 new_issue_relation = IssueRelation.new
663 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
662 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
664 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
663 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
665 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
664 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
666 new_issue_relation.issue_to = source_relation.issue_to
665 new_issue_relation.issue_to = source_relation.issue_to
667 end
666 end
668 new_issue.relations_from << new_issue_relation
667 new_issue.relations_from << new_issue_relation
669 end
668 end
670
669
671 issue.relations_to.each do |source_relation|
670 issue.relations_to.each do |source_relation|
672 new_issue_relation = IssueRelation.new
671 new_issue_relation = IssueRelation.new
673 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
672 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
674 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
673 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
675 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
674 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
676 new_issue_relation.issue_from = source_relation.issue_from
675 new_issue_relation.issue_from = source_relation.issue_from
677 end
676 end
678 new_issue.relations_to << new_issue_relation
677 new_issue.relations_to << new_issue_relation
679 end
678 end
680 end
679 end
681 end
680 end
682
681
683 # Copies members from +project+
682 # Copies members from +project+
684 def copy_members(project)
683 def copy_members(project)
685 project.memberships.each do |member|
684 project.memberships.each do |member|
686 new_member = Member.new
685 new_member = Member.new
687 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
686 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
688 # only copy non inherited roles
687 # only copy non inherited roles
689 # inherited roles will be added when copying the group membership
688 # inherited roles will be added when copying the group membership
690 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
689 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
691 next if role_ids.empty?
690 next if role_ids.empty?
692 new_member.role_ids = role_ids
691 new_member.role_ids = role_ids
693 new_member.project = self
692 new_member.project = self
694 self.members << new_member
693 self.members << new_member
695 end
694 end
696 end
695 end
697
696
698 # Copies queries from +project+
697 # Copies queries from +project+
699 def copy_queries(project)
698 def copy_queries(project)
700 project.queries.each do |query|
699 project.queries.each do |query|
701 new_query = Query.new
700 new_query = Query.new
702 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
701 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
703 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
702 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
704 new_query.project = self
703 new_query.project = self
705 self.queries << new_query
704 self.queries << new_query
706 end
705 end
707 end
706 end
708
707
709 # Copies boards from +project+
708 # Copies boards from +project+
710 def copy_boards(project)
709 def copy_boards(project)
711 project.boards.each do |board|
710 project.boards.each do |board|
712 new_board = Board.new
711 new_board = Board.new
713 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
712 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
714 new_board.project = self
713 new_board.project = self
715 self.boards << new_board
714 self.boards << new_board
716 end
715 end
717 end
716 end
718
717
719 def allowed_permissions
718 def allowed_permissions
720 @allowed_permissions ||= begin
719 @allowed_permissions ||= begin
721 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
720 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
722 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
721 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
723 end
722 end
724 end
723 end
725
724
726 def allowed_actions
725 def allowed_actions
727 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
726 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
728 end
727 end
729
728
730 # Returns all the active Systemwide and project specific activities
729 # Returns all the active Systemwide and project specific activities
731 def active_activities
730 def active_activities
732 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
731 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
733
732
734 if overridden_activity_ids.empty?
733 if overridden_activity_ids.empty?
735 return TimeEntryActivity.shared.active
734 return TimeEntryActivity.shared.active
736 else
735 else
737 return system_activities_and_project_overrides
736 return system_activities_and_project_overrides
738 end
737 end
739 end
738 end
740
739
741 # Returns all the Systemwide and project specific activities
740 # Returns all the Systemwide and project specific activities
742 # (inactive and active)
741 # (inactive and active)
743 def all_activities
742 def all_activities
744 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
743 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
745
744
746 if overridden_activity_ids.empty?
745 if overridden_activity_ids.empty?
747 return TimeEntryActivity.shared
746 return TimeEntryActivity.shared
748 else
747 else
749 return system_activities_and_project_overrides(true)
748 return system_activities_and_project_overrides(true)
750 end
749 end
751 end
750 end
752
751
753 # Returns the systemwide active activities merged with the project specific overrides
752 # Returns the systemwide active activities merged with the project specific overrides
754 def system_activities_and_project_overrides(include_inactive=false)
753 def system_activities_and_project_overrides(include_inactive=false)
755 if include_inactive
754 if include_inactive
756 return TimeEntryActivity.shared.
755 return TimeEntryActivity.shared.
757 find(:all,
756 find(:all,
758 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
757 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
759 self.time_entry_activities
758 self.time_entry_activities
760 else
759 else
761 return TimeEntryActivity.shared.active.
760 return TimeEntryActivity.shared.active.
762 find(:all,
761 find(:all,
763 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
762 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
764 self.time_entry_activities.active
763 self.time_entry_activities.active
765 end
764 end
766 end
765 end
767
766
768 # Archives subprojects recursively
767 # Archives subprojects recursively
769 def archive!
768 def archive!
770 children.each do |subproject|
769 children.each do |subproject|
771 subproject.send :archive!
770 subproject.send :archive!
772 end
771 end
773 update_attribute :status, STATUS_ARCHIVED
772 update_attribute :status, STATUS_ARCHIVED
774 end
773 end
775 end
774 end
General Comments 0
You need to be logged in to leave comments. Login now