##// END OF EJS Templates
Merged r4080 from trunk....
Eric Davis -
r4028:d1952aefb8e3
parent child
Show More
@@ -1,31 +1,23
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 module AdminHelper
18 module AdminHelper
19 def project_status_options_for_select(selected)
19 def project_status_options_for_select(selected)
20 options_for_select([[l(:label_all), ''],
20 options_for_select([[l(:label_all), ''],
21 [l(:status_active), 1]], selected)
21 [l(:status_active), 1]], selected)
22 end
22 end
23
24 def css_project_classes(project)
25 s = 'project'
26 s << ' root' if project.root?
27 s << ' child' if project.child?
28 s << (project.leaf? ? ' leaf' : ' parent')
29 s
30 end
31 end
23 end
@@ -1,713 +1,721
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
417 s = 'project'
418 s << ' root' if root?
419 s << ' child' if child?
420 s << (leaf? ? ' leaf' : ' parent')
421 s
422 end
423
416 # Return true if this project is allowed to do the specified action.
424 # Return true if this project is allowed to do the specified action.
417 # action can be:
425 # action can be:
418 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
426 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
419 # * a permission Symbol (eg. :edit_project)
427 # * a permission Symbol (eg. :edit_project)
420 def allows_to?(action)
428 def allows_to?(action)
421 if action.is_a? Hash
429 if action.is_a? Hash
422 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
430 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
423 else
431 else
424 allowed_permissions.include? action
432 allowed_permissions.include? action
425 end
433 end
426 end
434 end
427
435
428 def module_enabled?(module_name)
436 def module_enabled?(module_name)
429 module_name = module_name.to_s
437 module_name = module_name.to_s
430 enabled_modules.detect {|m| m.name == module_name}
438 enabled_modules.detect {|m| m.name == module_name}
431 end
439 end
432
440
433 def enabled_module_names=(module_names)
441 def enabled_module_names=(module_names)
434 if module_names && module_names.is_a?(Array)
442 if module_names && module_names.is_a?(Array)
435 module_names = module_names.collect(&:to_s)
443 module_names = module_names.collect(&:to_s)
436 # remove disabled modules
444 # remove disabled modules
437 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
445 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
438 # add new modules
446 # add new modules
439 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
447 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
440 else
448 else
441 enabled_modules.clear
449 enabled_modules.clear
442 end
450 end
443 end
451 end
444
452
445 # Returns an auto-generated project identifier based on the last identifier used
453 # Returns an auto-generated project identifier based on the last identifier used
446 def self.next_identifier
454 def self.next_identifier
447 p = Project.find(:first, :order => 'created_on DESC')
455 p = Project.find(:first, :order => 'created_on DESC')
448 p.nil? ? nil : p.identifier.to_s.succ
456 p.nil? ? nil : p.identifier.to_s.succ
449 end
457 end
450
458
451 # Copies and saves the Project instance based on the +project+.
459 # Copies and saves the Project instance based on the +project+.
452 # Duplicates the source project's:
460 # Duplicates the source project's:
453 # * Wiki
461 # * Wiki
454 # * Versions
462 # * Versions
455 # * Categories
463 # * Categories
456 # * Issues
464 # * Issues
457 # * Members
465 # * Members
458 # * Queries
466 # * Queries
459 #
467 #
460 # Accepts an +options+ argument to specify what to copy
468 # Accepts an +options+ argument to specify what to copy
461 #
469 #
462 # Examples:
470 # Examples:
463 # project.copy(1) # => copies everything
471 # project.copy(1) # => copies everything
464 # project.copy(1, :only => 'members') # => copies members only
472 # project.copy(1, :only => 'members') # => copies members only
465 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
473 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
466 def copy(project, options={})
474 def copy(project, options={})
467 project = project.is_a?(Project) ? project : Project.find(project)
475 project = project.is_a?(Project) ? project : Project.find(project)
468
476
469 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
477 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
470 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
478 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
471
479
472 Project.transaction do
480 Project.transaction do
473 if save
481 if save
474 reload
482 reload
475 to_be_copied.each do |name|
483 to_be_copied.each do |name|
476 send "copy_#{name}", project
484 send "copy_#{name}", project
477 end
485 end
478 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
486 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
479 save
487 save
480 end
488 end
481 end
489 end
482 end
490 end
483
491
484
492
485 # Copies +project+ and returns the new instance. This will not save
493 # Copies +project+ and returns the new instance. This will not save
486 # the copy
494 # the copy
487 def self.copy_from(project)
495 def self.copy_from(project)
488 begin
496 begin
489 project = project.is_a?(Project) ? project : Project.find(project)
497 project = project.is_a?(Project) ? project : Project.find(project)
490 if project
498 if project
491 # clear unique attributes
499 # clear unique attributes
492 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
500 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
493 copy = Project.new(attributes)
501 copy = Project.new(attributes)
494 copy.enabled_modules = project.enabled_modules
502 copy.enabled_modules = project.enabled_modules
495 copy.trackers = project.trackers
503 copy.trackers = project.trackers
496 copy.custom_values = project.custom_values.collect {|v| v.clone}
504 copy.custom_values = project.custom_values.collect {|v| v.clone}
497 copy.issue_custom_fields = project.issue_custom_fields
505 copy.issue_custom_fields = project.issue_custom_fields
498 return copy
506 return copy
499 else
507 else
500 return nil
508 return nil
501 end
509 end
502 rescue ActiveRecord::RecordNotFound
510 rescue ActiveRecord::RecordNotFound
503 return nil
511 return nil
504 end
512 end
505 end
513 end
506
514
507 private
515 private
508
516
509 # Destroys children before destroying self
517 # Destroys children before destroying self
510 def destroy_children
518 def destroy_children
511 children.each do |child|
519 children.each do |child|
512 child.destroy
520 child.destroy
513 end
521 end
514 end
522 end
515
523
516 # Copies wiki from +project+
524 # Copies wiki from +project+
517 def copy_wiki(project)
525 def copy_wiki(project)
518 # Check that the source project has a wiki first
526 # Check that the source project has a wiki first
519 unless project.wiki.nil?
527 unless project.wiki.nil?
520 self.wiki ||= Wiki.new
528 self.wiki ||= Wiki.new
521 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
529 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
522 wiki_pages_map = {}
530 wiki_pages_map = {}
523 project.wiki.pages.each do |page|
531 project.wiki.pages.each do |page|
524 # Skip pages without content
532 # Skip pages without content
525 next if page.content.nil?
533 next if page.content.nil?
526 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
534 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
527 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
535 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
528 new_wiki_page.content = new_wiki_content
536 new_wiki_page.content = new_wiki_content
529 wiki.pages << new_wiki_page
537 wiki.pages << new_wiki_page
530 wiki_pages_map[page.id] = new_wiki_page
538 wiki_pages_map[page.id] = new_wiki_page
531 end
539 end
532 wiki.save
540 wiki.save
533 # Reproduce page hierarchy
541 # Reproduce page hierarchy
534 project.wiki.pages.each do |page|
542 project.wiki.pages.each do |page|
535 if page.parent_id && wiki_pages_map[page.id]
543 if page.parent_id && wiki_pages_map[page.id]
536 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
544 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
537 wiki_pages_map[page.id].save
545 wiki_pages_map[page.id].save
538 end
546 end
539 end
547 end
540 end
548 end
541 end
549 end
542
550
543 # Copies versions from +project+
551 # Copies versions from +project+
544 def copy_versions(project)
552 def copy_versions(project)
545 project.versions.each do |version|
553 project.versions.each do |version|
546 new_version = Version.new
554 new_version = Version.new
547 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
555 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
548 self.versions << new_version
556 self.versions << new_version
549 end
557 end
550 end
558 end
551
559
552 # Copies issue categories from +project+
560 # Copies issue categories from +project+
553 def copy_issue_categories(project)
561 def copy_issue_categories(project)
554 project.issue_categories.each do |issue_category|
562 project.issue_categories.each do |issue_category|
555 new_issue_category = IssueCategory.new
563 new_issue_category = IssueCategory.new
556 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
564 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
557 self.issue_categories << new_issue_category
565 self.issue_categories << new_issue_category
558 end
566 end
559 end
567 end
560
568
561 # Copies issues from +project+
569 # Copies issues from +project+
562 def copy_issues(project)
570 def copy_issues(project)
563 # Stores the source issue id as a key and the copied issues as the
571 # Stores the source issue id as a key and the copied issues as the
564 # value. Used to map the two togeather for issue relations.
572 # value. Used to map the two togeather for issue relations.
565 issues_map = {}
573 issues_map = {}
566
574
567 # Get issues sorted by root_id, lft so that parent issues
575 # Get issues sorted by root_id, lft so that parent issues
568 # get copied before their children
576 # get copied before their children
569 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
577 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
570 new_issue = Issue.new
578 new_issue = Issue.new
571 new_issue.copy_from(issue)
579 new_issue.copy_from(issue)
572 new_issue.project = self
580 new_issue.project = self
573 # Reassign fixed_versions by name, since names are unique per
581 # Reassign fixed_versions by name, since names are unique per
574 # project and the versions for self are not yet saved
582 # project and the versions for self are not yet saved
575 if issue.fixed_version
583 if issue.fixed_version
576 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
584 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
577 end
585 end
578 # Reassign the category by name, since names are unique per
586 # Reassign the category by name, since names are unique per
579 # project and the categories for self are not yet saved
587 # project and the categories for self are not yet saved
580 if issue.category
588 if issue.category
581 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
589 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
582 end
590 end
583 # Parent issue
591 # Parent issue
584 if issue.parent_id
592 if issue.parent_id
585 if copied_parent = issues_map[issue.parent_id]
593 if copied_parent = issues_map[issue.parent_id]
586 new_issue.parent_issue_id = copied_parent.id
594 new_issue.parent_issue_id = copied_parent.id
587 end
595 end
588 end
596 end
589
597
590 self.issues << new_issue
598 self.issues << new_issue
591 issues_map[issue.id] = new_issue
599 issues_map[issue.id] = new_issue
592 end
600 end
593
601
594 # Relations after in case issues related each other
602 # Relations after in case issues related each other
595 project.issues.each do |issue|
603 project.issues.each do |issue|
596 new_issue = issues_map[issue.id]
604 new_issue = issues_map[issue.id]
597
605
598 # Relations
606 # Relations
599 issue.relations_from.each do |source_relation|
607 issue.relations_from.each do |source_relation|
600 new_issue_relation = IssueRelation.new
608 new_issue_relation = IssueRelation.new
601 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
609 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
602 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
610 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
603 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
611 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
604 new_issue_relation.issue_to = source_relation.issue_to
612 new_issue_relation.issue_to = source_relation.issue_to
605 end
613 end
606 new_issue.relations_from << new_issue_relation
614 new_issue.relations_from << new_issue_relation
607 end
615 end
608
616
609 issue.relations_to.each do |source_relation|
617 issue.relations_to.each do |source_relation|
610 new_issue_relation = IssueRelation.new
618 new_issue_relation = IssueRelation.new
611 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
619 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
612 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
620 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
613 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
621 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
614 new_issue_relation.issue_from = source_relation.issue_from
622 new_issue_relation.issue_from = source_relation.issue_from
615 end
623 end
616 new_issue.relations_to << new_issue_relation
624 new_issue.relations_to << new_issue_relation
617 end
625 end
618 end
626 end
619 end
627 end
620
628
621 # Copies members from +project+
629 # Copies members from +project+
622 def copy_members(project)
630 def copy_members(project)
623 project.memberships.each do |member|
631 project.memberships.each do |member|
624 new_member = Member.new
632 new_member = Member.new
625 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
633 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
626 # only copy non inherited roles
634 # only copy non inherited roles
627 # inherited roles will be added when copying the group membership
635 # inherited roles will be added when copying the group membership
628 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
636 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
629 next if role_ids.empty?
637 next if role_ids.empty?
630 new_member.role_ids = role_ids
638 new_member.role_ids = role_ids
631 new_member.project = self
639 new_member.project = self
632 self.members << new_member
640 self.members << new_member
633 end
641 end
634 end
642 end
635
643
636 # Copies queries from +project+
644 # Copies queries from +project+
637 def copy_queries(project)
645 def copy_queries(project)
638 project.queries.each do |query|
646 project.queries.each do |query|
639 new_query = Query.new
647 new_query = Query.new
640 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
648 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
641 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
649 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
642 new_query.project = self
650 new_query.project = self
643 self.queries << new_query
651 self.queries << new_query
644 end
652 end
645 end
653 end
646
654
647 # Copies boards from +project+
655 # Copies boards from +project+
648 def copy_boards(project)
656 def copy_boards(project)
649 project.boards.each do |board|
657 project.boards.each do |board|
650 new_board = Board.new
658 new_board = Board.new
651 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
659 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
652 new_board.project = self
660 new_board.project = self
653 self.boards << new_board
661 self.boards << new_board
654 end
662 end
655 end
663 end
656
664
657 def allowed_permissions
665 def allowed_permissions
658 @allowed_permissions ||= begin
666 @allowed_permissions ||= begin
659 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
667 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
660 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
668 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
661 end
669 end
662 end
670 end
663
671
664 def allowed_actions
672 def allowed_actions
665 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
673 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
666 end
674 end
667
675
668 # Returns all the active Systemwide and project specific activities
676 # Returns all the active Systemwide and project specific activities
669 def active_activities
677 def active_activities
670 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
678 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
671
679
672 if overridden_activity_ids.empty?
680 if overridden_activity_ids.empty?
673 return TimeEntryActivity.shared.active
681 return TimeEntryActivity.shared.active
674 else
682 else
675 return system_activities_and_project_overrides
683 return system_activities_and_project_overrides
676 end
684 end
677 end
685 end
678
686
679 # Returns all the Systemwide and project specific activities
687 # Returns all the Systemwide and project specific activities
680 # (inactive and active)
688 # (inactive and active)
681 def all_activities
689 def all_activities
682 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
690 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
683
691
684 if overridden_activity_ids.empty?
692 if overridden_activity_ids.empty?
685 return TimeEntryActivity.shared
693 return TimeEntryActivity.shared
686 else
694 else
687 return system_activities_and_project_overrides(true)
695 return system_activities_and_project_overrides(true)
688 end
696 end
689 end
697 end
690
698
691 # Returns the systemwide active activities merged with the project specific overrides
699 # Returns the systemwide active activities merged with the project specific overrides
692 def system_activities_and_project_overrides(include_inactive=false)
700 def system_activities_and_project_overrides(include_inactive=false)
693 if include_inactive
701 if include_inactive
694 return TimeEntryActivity.shared.
702 return TimeEntryActivity.shared.
695 find(:all,
703 find(:all,
696 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
704 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
697 self.time_entry_activities
705 self.time_entry_activities
698 else
706 else
699 return TimeEntryActivity.shared.active.
707 return TimeEntryActivity.shared.active.
700 find(:all,
708 find(:all,
701 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
709 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
702 self.time_entry_activities.active
710 self.time_entry_activities.active
703 end
711 end
704 end
712 end
705
713
706 # Archives subprojects recursively
714 # Archives subprojects recursively
707 def archive!
715 def archive!
708 children.each do |subproject|
716 children.each do |subproject|
709 subproject.send :archive!
717 subproject.send :archive!
710 end
718 end
711 update_attribute :status, STATUS_ARCHIVED
719 update_attribute :status, STATUS_ARCHIVED
712 end
720 end
713 end
721 end
@@ -1,46 +1,46
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>
2 <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>
3 </div>
3 </div>
4
4
5 <h2><%=l(:label_project_plural)%></h2>
5 <h2><%=l(:label_project_plural)%></h2>
6
6
7 <% form_tag({}, :method => :get) do %>
7 <% form_tag({}, :method => :get) do %>
8 <fieldset><legend><%= l(:label_filter_plural) %></legend>
8 <fieldset><legend><%= l(:label_filter_plural) %></legend>
9 <label><%= l(:field_status) %> :</label>
9 <label><%= l(:field_status) %> :</label>
10 <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
10 <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
11 <label><%= l(:label_project) %>:</label>
11 <label><%= l(:label_project) %>:</label>
12 <%= text_field_tag 'name', params[:name], :size => 30 %>
12 <%= text_field_tag 'name', params[:name], :size => 30 %>
13 <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
13 <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
14 </fieldset>
14 </fieldset>
15 <% end %>
15 <% end %>
16 &nbsp;
16 &nbsp;
17
17
18 <div class="autoscroll">
18 <div class="autoscroll">
19 <table class="list">
19 <table class="list">
20 <thead><tr>
20 <thead><tr>
21 <th><%=l(:label_project)%></th>
21 <th><%=l(:label_project)%></th>
22 <th><%=l(:field_description)%></th>
22 <th><%=l(:field_description)%></th>
23 <th><%=l(:field_is_public)%></th>
23 <th><%=l(:field_is_public)%></th>
24 <th><%=l(:field_created_on)%></th>
24 <th><%=l(:field_created_on)%></th>
25 <th></th>
25 <th></th>
26 </tr></thead>
26 </tr></thead>
27 <tbody>
27 <tbody>
28 <% project_tree(@projects) do |project, level| %>
28 <% project_tree(@projects) do |project, level| %>
29 <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
29 <tr class="<%= cycle("odd", "even") %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
30 <td class="name"><%= link_to_project(project, :action => 'settings') %></td>
30 <td class="name"><%= link_to_project(project, :action => 'settings') %></td>
31 <td><%= textilizable project.short_description, :project => project %></td>
31 <td><%= textilizable project.short_description, :project => project %></td>
32 <td align="center"><%= checked_image project.is_public? %></td>
32 <td align="center"><%= checked_image project.is_public? %></td>
33 <td align="center"><%= format_date(project.created_on) %></td>
33 <td align="center"><%= format_date(project.created_on) %></td>
34 <td class="buttons">
34 <td class="buttons">
35 <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
35 <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
36 <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
36 <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
37 <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
37 <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
38 <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
38 <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
39 </td>
39 </td>
40 </tr>
40 </tr>
41 <% end %>
41 <% end %>
42 </tbody>
42 </tbody>
43 </table>
43 </table>
44 </div>
44 </div>
45
45
46 <% html_title(l(:label_project_plural)) -%>
46 <% html_title(l(:label_project_plural)) -%>
General Comments 0
You need to be logged in to leave comments. Login now