Mercurial > hg > soundsoftware-site
comparison app/models/project.rb @ 514:7eba09d624db live
Merge
author | Chris Cannam |
---|---|
date | Thu, 14 Jul 2011 10:50:53 +0100 |
parents | 851510f1b535 |
children | 65abc6b39292 |
comparison
equal
deleted
inserted
replaced
512:b9aebdd7dd40 | 514:7eba09d624db |
---|---|
1 # redMine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006 Jean-Philippe Lang | 2 # Copyright (C) 2006-2011 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. |
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 | |
20 | |
19 # Project statuses | 21 # Project statuses |
20 STATUS_ACTIVE = 1 | 22 STATUS_ACTIVE = 1 |
21 STATUS_ARCHIVED = 9 | 23 STATUS_ARCHIVED = 9 |
22 | 24 |
23 # Maximum length for project identifiers | 25 # Maximum length for project identifiers |
39 has_many :issue_changes, :through => :issues, :source => :journals | 41 has_many :issue_changes, :through => :issues, :source => :journals |
40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" | 42 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" |
41 has_many :time_entries, :dependent => :delete_all | 43 has_many :time_entries, :dependent => :delete_all |
42 has_many :queries, :dependent => :delete_all | 44 has_many :queries, :dependent => :delete_all |
43 has_many :documents, :dependent => :destroy | 45 has_many :documents, :dependent => :destroy |
44 has_many :news, :dependent => :delete_all, :include => :author | 46 has_many :news, :dependent => :destroy, :include => :author |
45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" | 47 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" |
46 has_many :boards, :dependent => :destroy, :order => "position ASC" | 48 has_many :boards, :dependent => :destroy, :order => "position ASC" |
47 has_one :repository, :dependent => :destroy | 49 has_one :repository, :dependent => :destroy |
48 has_many :changesets, :through => :repository | 50 has_many :changesets, :through => :repository |
49 has_one :wiki, :dependent => :destroy | 51 has_one :wiki, :dependent => :destroy |
52 :class_name => 'IssueCustomField', | 54 :class_name => 'IssueCustomField', |
53 :order => "#{CustomField.table_name}.position", | 55 :order => "#{CustomField.table_name}.position", |
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", | 56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", |
55 :association_foreign_key => 'custom_field_id' | 57 :association_foreign_key => 'custom_field_id' |
56 | 58 |
57 acts_as_nested_set :order => 'name' | 59 acts_as_nested_set :order => 'name', :dependent => :destroy |
58 acts_as_attachable :view_permission => :view_files, | 60 acts_as_attachable :view_permission => :view_files, |
59 :delete_permission => :manage_files | 61 :delete_permission => :manage_files |
60 | 62 |
61 acts_as_customizable | 63 acts_as_customizable |
62 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil | 64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil |
63 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}"}, |
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}}, | 66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}}, |
65 :author => nil | 67 :author => nil |
66 | 68 |
67 attr_protected :status, :enabled_module_names | 69 attr_protected :status |
68 | 70 |
69 validates_presence_of :name, :identifier | 71 validates_presence_of :name, :identifier |
70 validates_uniqueness_of :identifier | 72 validates_uniqueness_of :identifier |
71 validates_associated :repository, :wiki | 73 validates_associated :repository, :wiki |
72 validates_length_of :name, :maximum => 255 | 74 validates_length_of :name, :maximum => 255 |
75 # donwcase letters, digits, dashes but not digits only | 77 # donwcase letters, digits, dashes but not digits only |
76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? } | 78 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? } |
77 # reserved words | 79 # reserved words |
78 validates_exclusion_of :identifier, :in => %w( new ) | 80 validates_exclusion_of :identifier, :in => %w( new ) |
79 | 81 |
80 before_destroy :delete_all_members, :destroy_children | 82 before_destroy :delete_all_members |
81 | 83 |
82 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] } } | 84 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] } } |
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} | 85 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} |
84 named_scope :all_public, { :conditions => { :is_public => true } } | 86 named_scope :all_public, { :conditions => { :is_public => true } } |
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } } | 87 named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} |
86 named_scope :visible_roots, lambda { { :conditions => Project.root_visible_by(User.current) } } | 88 named_scope :visible_roots, lambda { { :conditions => Project.root_visible_by(User.current) } } |
89 | |
90 def initialize(attributes = nil) | |
91 super | |
92 | |
93 initialized = (attributes || {}).stringify_keys | |
94 if !initialized.key?('identifier') && Setting.sequential_project_identifiers? | |
95 self.identifier = Project.next_identifier | |
96 end | |
97 if !initialized.key?('is_public') | |
98 self.is_public = Setting.default_projects_public? | |
99 end | |
100 if !initialized.key?('enabled_module_names') | |
101 self.enabled_module_names = Setting.default_projects_modules | |
102 end | |
103 if !initialized.key?('trackers') && !initialized.key?('tracker_ids') | |
104 self.trackers = Tracker.all | |
105 end | |
106 end | |
87 | 107 |
88 def identifier=(identifier) | 108 def identifier=(identifier) |
89 super unless identifier_frozen? | 109 super unless identifier_frozen? |
90 end | 110 end |
91 | 111 |
94 end | 114 end |
95 | 115 |
96 # returns latest created projects | 116 # returns latest created projects |
97 # non public projects will be returned only if user is a member of those | 117 # non public projects will be returned only if user is a member of those |
98 def self.latest(user=nil, count=5) | 118 def self.latest(user=nil, count=5) |
99 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC") | 119 visible(user).find(:all, :limit => count, :order => "created_on DESC") |
100 end | 120 end |
101 | 121 |
102 # Returns a SQL :conditions string used to find all active projects for the specified user. | 122 # Returns true if the project is visible to +user+ or to the current user. |
123 def visible?(user=User.current) | |
124 user.allowed_to?(:view_project, self) | |
125 end | |
126 | |
127 def self.visible_by(user=nil) | |
128 ActiveSupport::Deprecation.warn "Project.visible_by is deprecated and will be removed in Redmine 1.3.0. Use Project.visible_condition instead." | |
129 visible_condition(user || User.current) | |
130 end | |
131 | |
132 # Returns a SQL conditions string used to find all projects visible by the specified user. | |
103 # | 133 # |
104 # Examples: | 134 # Examples: |
105 # Projects.visible_by(admin) => "projects.status = 1" | 135 # Project.visible_condition(admin) => "projects.status = 1" |
106 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1" | 136 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))" |
107 def self.visible_by(user=nil) | 137 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))" |
108 user ||= User.current | 138 def self.visible_condition(user, options={}) |
109 if user && user.admin? | 139 allowed_to_condition(user, :view_project, options) |
110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" | |
111 elsif user && user.memberships.any? | |
112 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(',')}))" | |
113 else | |
114 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}" | |
115 end | |
116 end | 140 end |
117 | 141 |
118 def self.root_visible_by(user=nil) | 142 def self.root_visible_by(user=nil) |
119 return "#{Project.table_name}.parent_id IS NULL AND " + visible_by(user) | 143 return "#{Project.table_name}.parent_id IS NULL AND " + visible_by(user) |
120 end | 144 end |
121 | 145 |
146 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ | |
147 # | |
148 # Valid options: | |
149 # * :project => limit the condition to project | |
150 # * :with_subprojects => limit the condition to project and its subprojects | |
151 # * :member => limit the condition to the user projects | |
122 def self.allowed_to_condition(user, permission, options={}) | 152 def self.allowed_to_condition(user, permission, options={}) |
123 statements = [] | |
124 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" | 153 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" |
125 if perm = Redmine::AccessControl.permission(permission) | 154 if perm = Redmine::AccessControl.permission(permission) |
126 unless perm.project_module.nil? | 155 unless perm.project_module.nil? |
127 # If the permission belongs to a project module, make sure the module is enabled | 156 # If the permission belongs to a project module, make sure the module is enabled |
128 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" | 157 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" |
131 if options[:project] | 160 if options[:project] |
132 project_statement = "#{Project.table_name}.id = #{options[:project].id}" | 161 project_statement = "#{Project.table_name}.id = #{options[:project].id}" |
133 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] | 162 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] |
134 base_statement = "(#{project_statement}) AND (#{base_statement})" | 163 base_statement = "(#{project_statement}) AND (#{base_statement})" |
135 end | 164 end |
165 | |
136 if user.admin? | 166 if user.admin? |
137 # no restriction | 167 base_statement |
138 else | 168 else |
139 statements << "1=0" | 169 statement_by_role = {} |
170 unless options[:member] | |
171 role = user.logged? ? Role.non_member : Role.anonymous | |
172 if role.allowed_to?(permission) | |
173 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}" | |
174 end | |
175 end | |
140 if user.logged? | 176 if user.logged? |
141 if Role.non_member.allowed_to?(permission) && !options[:member] | 177 user.projects_by_role.each do |role, projects| |
142 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" | 178 if role.allowed_to?(permission) |
143 end | 179 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})" |
144 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id} | 180 end |
145 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any? | 181 end |
182 end | |
183 if statement_by_role.empty? | |
184 "1=0" | |
146 else | 185 else |
147 if Role.anonymous.allowed_to?(permission) && !options[:member] | 186 if block_given? |
148 # anonymous user allowed on public project | 187 statement_by_role.each do |role, statement| |
149 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" | 188 if s = yield(role, user) |
150 end | 189 statement_by_role[role] = "(#{statement} AND (#{s}))" |
151 end | 190 end |
152 end | 191 end |
153 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))" | 192 end |
193 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" | |
194 end | |
195 end | |
154 end | 196 end |
155 | 197 |
156 # Returns the Systemwide and project specific activities | 198 # Returns the Systemwide and project specific activities |
157 def activities(include_inactive=false) | 199 def activities(include_inactive=false) |
158 if include_inactive | 200 if include_inactive |
330 end | 372 end |
331 | 373 |
332 # Returns an array of the trackers used by the project and its active sub projects | 374 # Returns an array of the trackers used by the project and its active sub projects |
333 def rolled_up_trackers | 375 def rolled_up_trackers |
334 @rolled_up_trackers ||= | 376 @rolled_up_trackers ||= |
335 Tracker.find(:all, :include => :projects, | 377 Tracker.find(:all, :joins => :projects, |
336 :select => "DISTINCT #{Tracker.table_name}.*", | 378 :select => "DISTINCT #{Tracker.table_name}.*", |
337 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], | 379 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], |
338 :order => "#{Tracker.table_name}.position") | 380 :order => "#{Tracker.table_name}.position") |
339 end | 381 end |
340 | 382 |
356 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt]) | 398 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt]) |
357 end | 399 end |
358 | 400 |
359 # Returns a scope of the Versions used by the project | 401 # Returns a scope of the Versions used by the project |
360 def shared_versions | 402 def shared_versions |
361 @shared_versions ||= | 403 @shared_versions ||= begin |
404 r = root? ? self : root | |
362 Version.scoped(:include => :project, | 405 Version.scoped(:include => :project, |
363 :conditions => "#{Project.table_name}.id = #{id}" + | 406 :conditions => "#{Project.table_name}.id = #{id}" + |
364 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + | 407 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + |
365 " #{Version.table_name}.sharing = 'system'" + | 408 " #{Version.table_name}.sharing = 'system'" + |
366 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" + | 409 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + |
367 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + | 410 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + |
368 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + | 411 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + |
369 "))") | 412 "))") |
413 end | |
370 end | 414 end |
371 | 415 |
372 # Returns a hash of project users grouped by role | 416 # Returns a hash of project users grouped by role |
373 def users_by_role | 417 def users_by_role |
374 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| | 418 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| |
406 # Returns an array of all custom fields enabled for project issues | 450 # Returns an array of all custom fields enabled for project issues |
407 # (explictly associated custom fields and custom fields enabled for all projects) | 451 # (explictly associated custom fields and custom fields enabled for all projects) |
408 def all_issue_custom_fields | 452 def all_issue_custom_fields |
409 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort | 453 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort |
410 end | 454 end |
455 | |
456 # Returns an array of all custom fields enabled for project time entries | |
457 # (explictly associated custom fields and custom fields enabled for all projects) | |
458 def all_time_entry_custom_fields | |
459 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort | |
460 end | |
411 | 461 |
412 def project | 462 def project |
413 self | 463 self |
414 end | 464 end |
415 | 465 |
448 s | 498 s |
449 end | 499 end |
450 | 500 |
451 # The earliest start date of a project, based on it's issues and versions | 501 # The earliest start date of a project, based on it's issues and versions |
452 def start_date | 502 def start_date |
453 if module_enabled?(:issue_tracking) | 503 [ |
454 [ | 504 issues.minimum('start_date'), |
455 issues.minimum('start_date'), | 505 shared_versions.collect(&:effective_date), |
456 shared_versions.collect(&:effective_date), | 506 shared_versions.collect(&:start_date) |
457 shared_versions.collect {|v| v.fixed_issues.minimum('start_date')} | 507 ].flatten.compact.min |
458 ].flatten.compact.min | |
459 end | |
460 end | 508 end |
461 | 509 |
462 # The latest due date of an issue or version | 510 # The latest due date of an issue or version |
463 def due_date | 511 def due_date |
464 if module_enabled?(:issue_tracking) | 512 [ |
465 [ | 513 issues.maximum('due_date'), |
466 issues.maximum('due_date'), | 514 shared_versions.collect(&:effective_date), |
467 shared_versions.collect(&:effective_date), | 515 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} |
468 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} | 516 ].flatten.compact.max |
469 ].flatten.compact.max | |
470 end | |
471 end | 517 end |
472 | 518 |
473 def overdue? | 519 def overdue? |
474 active? && !due_date.nil? && (due_date < Date.today) | 520 active? && !due_date.nil? && (due_date < Date.today) |
475 end | 521 end |
509 enabled_modules.detect {|m| m.name == module_name} | 555 enabled_modules.detect {|m| m.name == module_name} |
510 end | 556 end |
511 | 557 |
512 def enabled_module_names=(module_names) | 558 def enabled_module_names=(module_names) |
513 if module_names && module_names.is_a?(Array) | 559 if module_names && module_names.is_a?(Array) |
514 module_names = module_names.collect(&:to_s) | 560 module_names = module_names.collect(&:to_s).reject(&:blank?) |
515 # remove disabled modules | 561 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)} |
516 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)} | |
517 # add new modules | |
518 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)} | |
519 else | 562 else |
520 enabled_modules.clear | 563 enabled_modules.clear |
521 end | 564 end |
522 end | 565 end |
523 | 566 |
567 # Returns an array of the enabled modules names | |
568 def enabled_module_names | |
569 enabled_modules.collect(&:name) | |
570 end | |
571 | |
572 # Enable a specific module | |
573 # | |
574 # Examples: | |
575 # project.enable_module!(:issue_tracking) | |
576 # project.enable_module!("issue_tracking") | |
577 def enable_module!(name) | |
578 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name) | |
579 end | |
580 | |
581 # Disable a module if it exists | |
582 # | |
583 # Examples: | |
584 # project.disable_module!(:issue_tracking) | |
585 # project.disable_module!("issue_tracking") | |
586 # project.disable_module!(project.enabled_modules.first) | |
587 def disable_module!(target) | |
588 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target) | |
589 target.destroy unless target.blank? | |
590 end | |
591 | |
592 safe_attributes 'name', | |
593 'description', | |
594 'homepage', | |
595 'is_public', | |
596 'identifier', | |
597 'custom_field_values', | |
598 'custom_fields', | |
599 'tracker_ids', | |
600 'issue_custom_field_ids' | |
601 | |
602 safe_attributes 'enabled_module_names', | |
603 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } | |
604 | |
524 # Returns an array of projects that are in this project's hierarchy | 605 # Returns an array of projects that are in this project's hierarchy |
525 # | 606 # |
526 # Example: parents, children, siblings | 607 # Example: parents, children, siblings |
527 def hierarchy | 608 def hierarchy |
528 parents = project.self_and_ancestors || [] | 609 parents = project.self_and_ancestors || [] |
604 end | 685 end |
605 end | 686 end |
606 | 687 |
607 private | 688 private |
608 | 689 |
609 # Destroys children before destroying self | |
610 def destroy_children | |
611 children.each do |child| | |
612 child.destroy | |
613 end | |
614 end | |
615 | |
616 # Copies wiki from +project+ | 690 # Copies wiki from +project+ |
617 def copy_wiki(project) | 691 def copy_wiki(project) |
618 # Check that the source project has a wiki first | 692 # Check that the source project has a wiki first |
619 unless project.wiki.nil? | 693 unless project.wiki.nil? |
620 self.wiki ||= Wiki.new | 694 self.wiki ||= Wiki.new |
657 self.issue_categories << new_issue_category | 731 self.issue_categories << new_issue_category |
658 end | 732 end |
659 end | 733 end |
660 | 734 |
661 # Copies issues from +project+ | 735 # Copies issues from +project+ |
736 # Note: issues assigned to a closed version won't be copied due to validation rules | |
662 def copy_issues(project) | 737 def copy_issues(project) |
663 # Stores the source issue id as a key and the copied issues as the | 738 # Stores the source issue id as a key and the copied issues as the |
664 # value. Used to map the two togeather for issue relations. | 739 # value. Used to map the two togeather for issue relations. |
665 issues_map = {} | 740 issues_map = {} |
666 | 741 |
686 new_issue.parent_issue_id = copied_parent.id | 761 new_issue.parent_issue_id = copied_parent.id |
687 end | 762 end |
688 end | 763 end |
689 | 764 |
690 self.issues << new_issue | 765 self.issues << new_issue |
691 issues_map[issue.id] = new_issue | 766 if new_issue.new_record? |
767 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info | |
768 else | |
769 issues_map[issue.id] = new_issue unless new_issue.new_record? | |
770 end | |
692 end | 771 end |
693 | 772 |
694 # Relations after in case issues related each other | 773 # Relations after in case issues related each other |
695 project.issues.each do |issue| | 774 project.issues.each do |issue| |
696 new_issue = issues_map[issue.id] | 775 new_issue = issues_map[issue.id] |
776 unless new_issue | |
777 # Issue was not copied | |
778 next | |
779 end | |
697 | 780 |
698 # Relations | 781 # Relations |
699 issue.relations_from.each do |source_relation| | 782 issue.relations_from.each do |source_relation| |
700 new_issue_relation = IssueRelation.new | 783 new_issue_relation = IssueRelation.new |
701 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") | 784 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") |
718 end | 801 end |
719 end | 802 end |
720 | 803 |
721 # Copies members from +project+ | 804 # Copies members from +project+ |
722 def copy_members(project) | 805 def copy_members(project) |
723 project.memberships.each do |member| | 806 # Copy users first, then groups to handle members with inherited and given roles |
807 members_to_copy = [] | |
808 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)} | |
809 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)} | |
810 | |
811 members_to_copy.each do |member| | |
724 new_member = Member.new | 812 new_member = Member.new |
725 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on") | 813 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on") |
726 # only copy non inherited roles | 814 # only copy non inherited roles |
727 # inherited roles will be added when copying the group membership | 815 # inherited roles will be added when copying the group membership |
728 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id) | 816 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id) |