##// END OF EJS Templates
Moved controller code to new method Project#add_default_member....
Jean-Philippe Lang -
r13160:4f4a019bebd0
parent child
Show More
@@ -1,256 +1,253
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ProjectsController < ApplicationController
19 19 menu_item :overview
20 20 menu_item :settings, :only => :settings
21 21
22 22 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
23 23 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
24 24 before_filter :authorize_global, :only => [:new, :create]
25 25 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
26 26 accept_rss_auth :index
27 27 accept_api_auth :index, :show, :create, :update, :destroy
28 28
29 29 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
30 30 if controller.request.post?
31 31 controller.send :expire_action, :controller => 'welcome', :action => 'robots'
32 32 end
33 33 end
34 34
35 35 helper :custom_fields
36 36 helper :issues
37 37 helper :queries
38 38 helper :repositories
39 39 helper :members
40 40
41 41 # Lists visible projects
42 42 def index
43 43 scope = Project.visible.sorted
44 44
45 45 respond_to do |format|
46 46 format.html {
47 47 unless params[:closed]
48 48 scope = scope.active
49 49 end
50 50 @projects = scope.to_a
51 51 }
52 52 format.api {
53 53 @offset, @limit = api_offset_and_limit
54 54 @project_count = scope.count
55 55 @projects = scope.offset(@offset).limit(@limit).to_a
56 56 }
57 57 format.atom {
58 58 projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a
59 59 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 60 }
61 61 end
62 62 end
63 63
64 64 def new
65 65 @issue_custom_fields = IssueCustomField.sorted.to_a
66 66 @trackers = Tracker.sorted.to_a
67 67 @project = Project.new
68 68 @project.safe_attributes = params[:project]
69 69 end
70 70
71 71 def create
72 72 @issue_custom_fields = IssueCustomField.sorted.to_a
73 73 @trackers = Tracker.sorted.to_a
74 74 @project = Project.new
75 75 @project.safe_attributes = params[:project]
76 76
77 77 if validate_parent_id && @project.save
78 78 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
79 # Add current user as a project member if current user is not admin
80 79 unless User.current.admin?
81 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
82 m = Member.new(:user => User.current, :roles => [r])
83 @project.members << m
80 @project.add_default_member(User.current)
84 81 end
85 82 respond_to do |format|
86 83 format.html {
87 84 flash[:notice] = l(:notice_successful_create)
88 85 if params[:continue]
89 86 attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}
90 87 redirect_to new_project_path(attrs)
91 88 else
92 89 redirect_to settings_project_path(@project)
93 90 end
94 91 }
95 92 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
96 93 end
97 94 else
98 95 respond_to do |format|
99 96 format.html { render :action => 'new' }
100 97 format.api { render_validation_errors(@project) }
101 98 end
102 99 end
103 100 end
104 101
105 102 def copy
106 103 @issue_custom_fields = IssueCustomField.sorted.to_a
107 104 @trackers = Tracker.sorted.to_a
108 105 @source_project = Project.find(params[:id])
109 106 if request.get?
110 107 @project = Project.copy_from(@source_project)
111 108 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
112 109 else
113 110 Mailer.with_deliveries(params[:notifications] == '1') do
114 111 @project = Project.new
115 112 @project.safe_attributes = params[:project]
116 113 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
117 114 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
118 115 flash[:notice] = l(:notice_successful_create)
119 116 redirect_to settings_project_path(@project)
120 117 elsif !@project.new_record?
121 118 # Project was created
122 119 # But some objects were not copied due to validation failures
123 120 # (eg. issues from disabled trackers)
124 121 # TODO: inform about that
125 122 redirect_to settings_project_path(@project)
126 123 end
127 124 end
128 125 end
129 126 rescue ActiveRecord::RecordNotFound
130 127 # source_project not found
131 128 render_404
132 129 end
133 130
134 131 # Show @project
135 132 def show
136 133 # try to redirect to the requested menu item
137 134 if params[:jump] && redirect_to_project_menu_item(@project, params[:jump])
138 135 return
139 136 end
140 137
141 138 @users_by_role = @project.users_by_role
142 139 @subprojects = @project.children.visible.to_a
143 140 @news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").to_a
144 141 @trackers = @project.rolled_up_trackers
145 142
146 143 cond = @project.project_condition(Setting.display_subprojects_issues?)
147 144
148 145 @open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count
149 146 @total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count
150 147
151 148 if User.current.allowed_to?(:view_time_entries, @project)
152 149 @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
153 150 end
154 151
155 152 @key = User.current.rss_key
156 153
157 154 respond_to do |format|
158 155 format.html
159 156 format.api
160 157 end
161 158 end
162 159
163 160 def settings
164 161 @issue_custom_fields = IssueCustomField.sorted.to_a
165 162 @issue_category ||= IssueCategory.new
166 163 @member ||= @project.members.new
167 164 @trackers = Tracker.sorted.to_a
168 165 @wiki ||= @project.wiki
169 166 end
170 167
171 168 def edit
172 169 end
173 170
174 171 def update
175 172 @project.safe_attributes = params[:project]
176 173 if validate_parent_id && @project.save
177 174 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
178 175 respond_to do |format|
179 176 format.html {
180 177 flash[:notice] = l(:notice_successful_update)
181 178 redirect_to settings_project_path(@project)
182 179 }
183 180 format.api { render_api_ok }
184 181 end
185 182 else
186 183 respond_to do |format|
187 184 format.html {
188 185 settings
189 186 render :action => 'settings'
190 187 }
191 188 format.api { render_validation_errors(@project) }
192 189 end
193 190 end
194 191 end
195 192
196 193 def modules
197 194 @project.enabled_module_names = params[:enabled_module_names]
198 195 flash[:notice] = l(:notice_successful_update)
199 196 redirect_to settings_project_path(@project, :tab => 'modules')
200 197 end
201 198
202 199 def archive
203 200 unless @project.archive
204 201 flash[:error] = l(:error_can_not_archive_project)
205 202 end
206 203 redirect_to admin_projects_path(:status => params[:status])
207 204 end
208 205
209 206 def unarchive
210 207 unless @project.active?
211 208 @project.unarchive
212 209 end
213 210 redirect_to admin_projects_path(:status => params[:status])
214 211 end
215 212
216 213 def close
217 214 @project.close
218 215 redirect_to project_path(@project)
219 216 end
220 217
221 218 def reopen
222 219 @project.reopen
223 220 redirect_to project_path(@project)
224 221 end
225 222
226 223 # Delete @project
227 224 def destroy
228 225 @project_to_destroy = @project
229 226 if api_request? || params[:confirm]
230 227 @project_to_destroy.destroy
231 228 respond_to do |format|
232 229 format.html { redirect_to admin_projects_path }
233 230 format.api { render_api_ok }
234 231 end
235 232 end
236 233 # hide project in layout
237 234 @project = nil
238 235 end
239 236
240 237 private
241 238
242 239 # Validates parent_id param according to user's permissions
243 240 # TODO: move it to Project model in a validation that depends on User.current
244 241 def validate_parent_id
245 242 return true if User.current.admin?
246 243 parent_id = params[:project] && params[:project][:parent_id]
247 244 if parent_id || @project.new_record?
248 245 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
249 246 unless @project.allowed_parents.include?(parent)
250 247 @project.errors.add :parent_id, :invalid
251 248 return false
252 249 end
253 250 end
254 251 true
255 252 end
256 253 end
@@ -1,1072 +1,1081
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Project < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20
21 21 # Project statuses
22 22 STATUS_ACTIVE = 1
23 23 STATUS_CLOSED = 5
24 24 STATUS_ARCHIVED = 9
25 25
26 26 # Maximum length for project identifiers
27 27 IDENTIFIER_MAX_LENGTH = 100
28 28
29 29 # Specific overridden Activities
30 30 has_many :time_entry_activities
31 31 has_many :members,
32 32 lambda { joins(:principal, :roles).
33 33 where("#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}") }
34 34 has_many :memberships, :class_name => 'Member'
35 35 has_many :member_principals,
36 36 lambda { joins(:principal).
37 37 where("#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}")},
38 38 :class_name => 'Member'
39 39 has_many :enabled_modules, :dependent => :delete_all
40 40 has_and_belongs_to_many :trackers, lambda {order(:position)}
41 41 has_many :issues, :dependent => :destroy
42 42 has_many :issue_changes, :through => :issues, :source => :journals
43 43 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
44 44 has_many :time_entries, :dependent => :destroy
45 45 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
46 46 has_many :documents, :dependent => :destroy
47 47 has_many :news, lambda {includes(:author)}, :dependent => :destroy
48 48 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
49 49 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
50 50 has_one :repository, lambda {where(["is_default = ?", true])}
51 51 has_many :repositories, :dependent => :destroy
52 52 has_many :changesets, :through => :repository
53 53 has_one :wiki, :dependent => :destroy
54 54 # Custom field for the project issues
55 55 has_and_belongs_to_many :issue_custom_fields,
56 56 lambda {order("#{CustomField.table_name}.position")},
57 57 :class_name => 'IssueCustomField',
58 58 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
59 59 :association_foreign_key => 'custom_field_id'
60 60
61 61 acts_as_nested_set :dependent => :destroy
62 62 acts_as_attachable :view_permission => :view_files,
63 63 :delete_permission => :manage_files
64 64
65 65 acts_as_customizable
66 66 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
67 67 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
68 68 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
69 69 :author => nil
70 70
71 71 attr_protected :status
72 72
73 73 validates_presence_of :name, :identifier
74 74 validates_uniqueness_of :identifier
75 75 validates_associated :repository, :wiki
76 76 validates_length_of :name, :maximum => 255
77 77 validates_length_of :homepage, :maximum => 255
78 78 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
79 79 # downcase letters, digits, dashes but not digits only
80 80 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
81 81 # reserved words
82 82 validates_exclusion_of :identifier, :in => %w( new )
83 83
84 84 after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?}
85 85 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
86 86 before_destroy :delete_all_members
87 87
88 88 scope :has_module, lambda {|mod|
89 89 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
90 90 }
91 91 scope :active, lambda { where(:status => STATUS_ACTIVE) }
92 92 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
93 93 scope :all_public, lambda { where(:is_public => true) }
94 94 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
95 95 scope :allowed_to, lambda {|*args|
96 96 user = User.current
97 97 permission = nil
98 98 if args.first.is_a?(Symbol)
99 99 permission = args.shift
100 100 else
101 101 user = args.shift
102 102 permission = args.shift
103 103 end
104 104 where(Project.allowed_to_condition(user, permission, *args))
105 105 }
106 106 scope :like, lambda {|arg|
107 107 if arg.blank?
108 108 where(nil)
109 109 else
110 110 pattern = "%#{arg.to_s.strip.downcase}%"
111 111 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
112 112 end
113 113 }
114 114 scope :sorted, lambda {order(:lft)}
115 115
116 116 def initialize(attributes=nil, *args)
117 117 super
118 118
119 119 initialized = (attributes || {}).stringify_keys
120 120 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
121 121 self.identifier = Project.next_identifier
122 122 end
123 123 if !initialized.key?('is_public')
124 124 self.is_public = Setting.default_projects_public?
125 125 end
126 126 if !initialized.key?('enabled_module_names')
127 127 self.enabled_module_names = Setting.default_projects_modules
128 128 end
129 129 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
130 130 default = Setting.default_projects_tracker_ids
131 131 if default.is_a?(Array)
132 132 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
133 133 else
134 134 self.trackers = Tracker.sorted.to_a
135 135 end
136 136 end
137 137 end
138 138
139 139 def identifier=(identifier)
140 140 super unless identifier_frozen?
141 141 end
142 142
143 143 def identifier_frozen?
144 144 errors[:identifier].blank? && !(new_record? || identifier.blank?)
145 145 end
146 146
147 147 # returns latest created projects
148 148 # non public projects will be returned only if user is a member of those
149 149 def self.latest(user=nil, count=5)
150 150 visible(user).limit(count).order("created_on DESC").to_a
151 151 end
152 152
153 153 # Returns true if the project is visible to +user+ or to the current user.
154 154 def visible?(user=User.current)
155 155 user.allowed_to?(:view_project, self)
156 156 end
157 157
158 158 # Returns a SQL conditions string used to find all projects visible by the specified user.
159 159 #
160 160 # Examples:
161 161 # Project.visible_condition(admin) => "projects.status = 1"
162 162 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
163 163 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
164 164 def self.visible_condition(user, options={})
165 165 allowed_to_condition(user, :view_project, options)
166 166 end
167 167
168 168 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
169 169 #
170 170 # Valid options:
171 171 # * :project => limit the condition to project
172 172 # * :with_subprojects => limit the condition to project and its subprojects
173 173 # * :member => limit the condition to the user projects
174 174 def self.allowed_to_condition(user, permission, options={})
175 175 perm = Redmine::AccessControl.permission(permission)
176 176 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
177 177 if perm && perm.project_module
178 178 # If the permission belongs to a project module, make sure the module is enabled
179 179 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
180 180 end
181 181 if project = options[:project]
182 182 project_statement = project.project_condition(options[:with_subprojects])
183 183 base_statement = "(#{project_statement}) AND (#{base_statement})"
184 184 end
185 185
186 186 if user.admin?
187 187 base_statement
188 188 else
189 189 statement_by_role = {}
190 190 unless options[:member]
191 191 role = user.builtin_role
192 192 if role.allowed_to?(permission)
193 193 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
194 194 end
195 195 end
196 196 user.projects_by_role.each do |role, projects|
197 197 if role.allowed_to?(permission) && projects.any?
198 198 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
199 199 end
200 200 end
201 201 if statement_by_role.empty?
202 202 "1=0"
203 203 else
204 204 if block_given?
205 205 statement_by_role.each do |role, statement|
206 206 if s = yield(role, user)
207 207 statement_by_role[role] = "(#{statement} AND (#{s}))"
208 208 end
209 209 end
210 210 end
211 211 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
212 212 end
213 213 end
214 214 end
215 215
216 216 def override_roles(role)
217 217 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
218 218 member = member_principals.where("#{Principal.table_name}.type = ?", group_class.name).first
219 219 member ? member.roles.to_a : [role]
220 220 end
221 221
222 222 def principals
223 223 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
224 224 end
225 225
226 226 def users
227 227 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
228 228 end
229 229
230 230 # Returns the Systemwide and project specific activities
231 231 def activities(include_inactive=false)
232 232 if include_inactive
233 233 return all_activities
234 234 else
235 235 return active_activities
236 236 end
237 237 end
238 238
239 239 # Will create a new Project specific Activity or update an existing one
240 240 #
241 241 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
242 242 # does not successfully save.
243 243 def update_or_create_time_entry_activity(id, activity_hash)
244 244 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
245 245 self.create_time_entry_activity_if_needed(activity_hash)
246 246 else
247 247 activity = project.time_entry_activities.find_by_id(id.to_i)
248 248 activity.update_attributes(activity_hash) if activity
249 249 end
250 250 end
251 251
252 252 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
253 253 #
254 254 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
255 255 # does not successfully save.
256 256 def create_time_entry_activity_if_needed(activity)
257 257 if activity['parent_id']
258 258 parent_activity = TimeEntryActivity.find(activity['parent_id'])
259 259 activity['name'] = parent_activity.name
260 260 activity['position'] = parent_activity.position
261 261 if Enumeration.overriding_change?(activity, parent_activity)
262 262 project_activity = self.time_entry_activities.create(activity)
263 263 if project_activity.new_record?
264 264 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
265 265 else
266 266 self.time_entries.
267 267 where(["activity_id = ?", parent_activity.id]).
268 268 update_all("activity_id = #{project_activity.id}")
269 269 end
270 270 end
271 271 end
272 272 end
273 273
274 274 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
275 275 #
276 276 # Examples:
277 277 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
278 278 # project.project_condition(false) => "projects.id = 1"
279 279 def project_condition(with_subprojects)
280 280 cond = "#{Project.table_name}.id = #{id}"
281 281 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
282 282 cond
283 283 end
284 284
285 285 def self.find(*args)
286 286 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
287 287 project = find_by_identifier(*args)
288 288 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
289 289 project
290 290 else
291 291 super
292 292 end
293 293 end
294 294
295 295 def self.find_by_param(*args)
296 296 self.find(*args)
297 297 end
298 298
299 299 alias :base_reload :reload
300 300 def reload(*args)
301 301 @principals = nil
302 302 @users = nil
303 303 @shared_versions = nil
304 304 @rolled_up_versions = nil
305 305 @rolled_up_trackers = nil
306 306 @all_issue_custom_fields = nil
307 307 @all_time_entry_custom_fields = nil
308 308 @to_param = nil
309 309 @allowed_parents = nil
310 310 @allowed_permissions = nil
311 311 @actions_allowed = nil
312 312 @start_date = nil
313 313 @due_date = nil
314 314 @override_members = nil
315 315 @assignable_users = nil
316 316 base_reload(*args)
317 317 end
318 318
319 319 def to_param
320 320 # id is used for projects with a numeric identifier (compatibility)
321 321 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
322 322 end
323 323
324 324 def active?
325 325 self.status == STATUS_ACTIVE
326 326 end
327 327
328 328 def archived?
329 329 self.status == STATUS_ARCHIVED
330 330 end
331 331
332 332 # Archives the project and its descendants
333 333 def archive
334 334 # Check that there is no issue of a non descendant project that is assigned
335 335 # to one of the project or descendant versions
336 336 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
337 337 if v_ids.any? &&
338 338 Issue.
339 339 includes(:project).
340 340 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
341 341 where("#{Issue.table_name}.fixed_version_id IN (?)", v_ids).
342 342 exists?
343 343 return false
344 344 end
345 345 Project.transaction do
346 346 archive!
347 347 end
348 348 true
349 349 end
350 350
351 351 # Unarchives the project
352 352 # All its ancestors must be active
353 353 def unarchive
354 354 return false if ancestors.detect {|a| !a.active?}
355 355 update_attribute :status, STATUS_ACTIVE
356 356 end
357 357
358 358 def close
359 359 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
360 360 end
361 361
362 362 def reopen
363 363 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
364 364 end
365 365
366 366 # Returns an array of projects the project can be moved to
367 367 # by the current user
368 368 def allowed_parents
369 369 return @allowed_parents if @allowed_parents
370 370 @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).to_a
371 371 @allowed_parents = @allowed_parents - self_and_descendants
372 372 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
373 373 @allowed_parents << nil
374 374 end
375 375 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
376 376 @allowed_parents << parent
377 377 end
378 378 @allowed_parents
379 379 end
380 380
381 381 # Sets the parent of the project with authorization check
382 382 def set_allowed_parent!(p)
383 383 unless p.nil? || p.is_a?(Project)
384 384 if p.to_s.blank?
385 385 p = nil
386 386 else
387 387 p = Project.find_by_id(p)
388 388 return false unless p
389 389 end
390 390 end
391 391 if p.nil?
392 392 if !new_record? && allowed_parents.empty?
393 393 return false
394 394 end
395 395 elsif !allowed_parents.include?(p)
396 396 return false
397 397 end
398 398 set_parent!(p)
399 399 end
400 400
401 401 # Sets the parent of the project
402 402 # Argument can be either a Project, a String, a Fixnum or nil
403 403 def set_parent!(p)
404 404 unless p.nil? || p.is_a?(Project)
405 405 if p.to_s.blank?
406 406 p = nil
407 407 else
408 408 p = Project.find_by_id(p)
409 409 return false unless p
410 410 end
411 411 end
412 412 if p == parent && !p.nil?
413 413 # Nothing to do
414 414 true
415 415 elsif p.nil? || (p.active? && move_possible?(p))
416 416 set_or_update_position_under(p)
417 417 Issue.update_versions_from_hierarchy_change(self)
418 418 true
419 419 else
420 420 # Can not move to the given target
421 421 false
422 422 end
423 423 end
424 424
425 425 # Recalculates all lft and rgt values based on project names
426 426 # Unlike Project.rebuild!, these values are recalculated even if the tree "looks" valid
427 427 # Used in BuildProjectsTree migration
428 428 def self.rebuild_tree!
429 429 transaction do
430 430 update_all "lft = NULL, rgt = NULL"
431 431 rebuild!(false)
432 432 all.each { |p| p.set_or_update_position_under(p.parent) }
433 433 end
434 434 end
435 435
436 436 # Returns an array of the trackers used by the project and its active sub projects
437 437 def rolled_up_trackers
438 438 @rolled_up_trackers ||=
439 439 Tracker.
440 440 joins(:projects).
441 441 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
442 442 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED).
443 443 uniq.
444 444 sorted.
445 445 to_a
446 446 end
447 447
448 448 # Closes open and locked project versions that are completed
449 449 def close_completed_versions
450 450 Version.transaction do
451 451 versions.where(:status => %w(open locked)).each do |version|
452 452 if version.completed?
453 453 version.update_attribute(:status, 'closed')
454 454 end
455 455 end
456 456 end
457 457 end
458 458
459 459 # Returns a scope of the Versions on subprojects
460 460 def rolled_up_versions
461 461 @rolled_up_versions ||=
462 462 Version.
463 463 joins(:project).
464 464 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
465 465 end
466 466
467 467 # Returns a scope of the Versions used by the project
468 468 def shared_versions
469 469 if new_record?
470 470 Version.
471 471 joins(:project).
472 472 preload(:project).
473 473 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
474 474 else
475 475 @shared_versions ||= begin
476 476 r = root? ? self : root
477 477 Version.
478 478 joins(:project).
479 479 preload(:project).
480 480 where("#{Project.table_name}.id = #{id}" +
481 481 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
482 482 " #{Version.table_name}.sharing = 'system'" +
483 483 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
484 484 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
485 485 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
486 486 "))")
487 487 end
488 488 end
489 489 end
490 490
491 491 # Returns a hash of project users grouped by role
492 492 def users_by_role
493 493 members.includes(:user, :roles).inject({}) do |h, m|
494 494 m.roles.each do |r|
495 495 h[r] ||= []
496 496 h[r] << m.user
497 497 end
498 498 h
499 499 end
500 500 end
501 501
502 # Adds user as a project member with the default role
503 # Used for when a non-admin user creates a project
504 def add_default_member(user)
505 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
506 member = Member.new(:project => self, :principal => user, :roles => [role])
507 self.members << member
508 member
509 end
510
502 511 # Deletes all project's members
503 512 def delete_all_members
504 513 me, mr = Member.table_name, MemberRole.table_name
505 514 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
506 515 Member.delete_all(['project_id = ?', id])
507 516 end
508 517
509 518 # Return a Principal scope of users/groups issues can be assigned to
510 519 def assignable_users
511 520 types = ['User']
512 521 types << 'Group' if Setting.issue_group_assignment?
513 522
514 523 @assignable_users ||= Principal.
515 524 active.
516 525 joins(:members => :roles).
517 526 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
518 527 uniq.
519 528 sorted
520 529 end
521 530
522 531 # Returns the mail addresses of users that should be always notified on project events
523 532 def recipients
524 533 notified_users.collect {|user| user.mail}
525 534 end
526 535
527 536 # Returns the users that should be notified on project events
528 537 def notified_users
529 538 # TODO: User part should be extracted to User#notify_about?
530 539 members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
531 540 end
532 541
533 542 # Returns a scope of all custom fields enabled for project issues
534 543 # (explicitly associated custom fields and custom fields enabled for all projects)
535 544 def all_issue_custom_fields
536 545 @all_issue_custom_fields ||= IssueCustomField.
537 546 sorted.
538 547 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
539 548 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
540 549 " WHERE cfp.project_id = ?)", true, id)
541 550 end
542 551
543 552 # Returns an array of all custom fields enabled for project time entries
544 553 # (explictly associated custom fields and custom fields enabled for all projects)
545 554 def all_time_entry_custom_fields
546 555 @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
547 556 end
548 557
549 558 def project
550 559 self
551 560 end
552 561
553 562 def <=>(project)
554 563 name.downcase <=> project.name.downcase
555 564 end
556 565
557 566 def to_s
558 567 name
559 568 end
560 569
561 570 # Returns a short description of the projects (first lines)
562 571 def short_description(length = 255)
563 572 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
564 573 end
565 574
566 575 def css_classes
567 576 s = 'project'
568 577 s << ' root' if root?
569 578 s << ' child' if child?
570 579 s << (leaf? ? ' leaf' : ' parent')
571 580 unless active?
572 581 if archived?
573 582 s << ' archived'
574 583 else
575 584 s << ' closed'
576 585 end
577 586 end
578 587 s
579 588 end
580 589
581 590 # The earliest start date of a project, based on it's issues and versions
582 591 def start_date
583 592 @start_date ||= [
584 593 issues.minimum('start_date'),
585 594 shared_versions.minimum('effective_date'),
586 595 Issue.fixed_version(shared_versions).minimum('start_date')
587 596 ].compact.min
588 597 end
589 598
590 599 # The latest due date of an issue or version
591 600 def due_date
592 601 @due_date ||= [
593 602 issues.maximum('due_date'),
594 603 shared_versions.maximum('effective_date'),
595 604 Issue.fixed_version(shared_versions).maximum('due_date')
596 605 ].compact.max
597 606 end
598 607
599 608 def overdue?
600 609 active? && !due_date.nil? && (due_date < Date.today)
601 610 end
602 611
603 612 # Returns the percent completed for this project, based on the
604 613 # progress on it's versions.
605 614 def completed_percent(options={:include_subprojects => false})
606 615 if options.delete(:include_subprojects)
607 616 total = self_and_descendants.collect(&:completed_percent).sum
608 617
609 618 total / self_and_descendants.count
610 619 else
611 620 if versions.count > 0
612 621 total = versions.collect(&:completed_percent).sum
613 622
614 623 total / versions.count
615 624 else
616 625 100
617 626 end
618 627 end
619 628 end
620 629
621 630 # Return true if this project allows to do the specified action.
622 631 # action can be:
623 632 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
624 633 # * a permission Symbol (eg. :edit_project)
625 634 def allows_to?(action)
626 635 if archived?
627 636 # No action allowed on archived projects
628 637 return false
629 638 end
630 639 unless active? || Redmine::AccessControl.read_action?(action)
631 640 # No write action allowed on closed projects
632 641 return false
633 642 end
634 643 # No action allowed on disabled modules
635 644 if action.is_a? Hash
636 645 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
637 646 else
638 647 allowed_permissions.include? action
639 648 end
640 649 end
641 650
642 651 # Return the enabled module with the given name
643 652 # or nil if the module is not enabled for the project
644 653 def enabled_module(name)
645 654 name = name.to_s
646 655 enabled_modules.detect {|m| m.name == name}
647 656 end
648 657
649 658 # Return true if the module with the given name is enabled
650 659 def module_enabled?(name)
651 660 enabled_module(name).present?
652 661 end
653 662
654 663 def enabled_module_names=(module_names)
655 664 if module_names && module_names.is_a?(Array)
656 665 module_names = module_names.collect(&:to_s).reject(&:blank?)
657 666 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
658 667 else
659 668 enabled_modules.clear
660 669 end
661 670 end
662 671
663 672 # Returns an array of the enabled modules names
664 673 def enabled_module_names
665 674 enabled_modules.collect(&:name)
666 675 end
667 676
668 677 # Enable a specific module
669 678 #
670 679 # Examples:
671 680 # project.enable_module!(:issue_tracking)
672 681 # project.enable_module!("issue_tracking")
673 682 def enable_module!(name)
674 683 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
675 684 end
676 685
677 686 # Disable a module if it exists
678 687 #
679 688 # Examples:
680 689 # project.disable_module!(:issue_tracking)
681 690 # project.disable_module!("issue_tracking")
682 691 # project.disable_module!(project.enabled_modules.first)
683 692 def disable_module!(target)
684 693 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
685 694 target.destroy unless target.blank?
686 695 end
687 696
688 697 safe_attributes 'name',
689 698 'description',
690 699 'homepage',
691 700 'is_public',
692 701 'identifier',
693 702 'custom_field_values',
694 703 'custom_fields',
695 704 'tracker_ids',
696 705 'issue_custom_field_ids'
697 706
698 707 safe_attributes 'enabled_module_names',
699 708 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
700 709
701 710 safe_attributes 'inherit_members',
702 711 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
703 712
704 713 # Returns an array of projects that are in this project's hierarchy
705 714 #
706 715 # Example: parents, children, siblings
707 716 def hierarchy
708 717 parents = project.self_and_ancestors || []
709 718 descendants = project.descendants || []
710 719 project_hierarchy = parents | descendants # Set union
711 720 end
712 721
713 722 # Returns an auto-generated project identifier based on the last identifier used
714 723 def self.next_identifier
715 724 p = Project.order('id DESC').first
716 725 p.nil? ? nil : p.identifier.to_s.succ
717 726 end
718 727
719 728 # Copies and saves the Project instance based on the +project+.
720 729 # Duplicates the source project's:
721 730 # * Wiki
722 731 # * Versions
723 732 # * Categories
724 733 # * Issues
725 734 # * Members
726 735 # * Queries
727 736 #
728 737 # Accepts an +options+ argument to specify what to copy
729 738 #
730 739 # Examples:
731 740 # project.copy(1) # => copies everything
732 741 # project.copy(1, :only => 'members') # => copies members only
733 742 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
734 743 def copy(project, options={})
735 744 project = project.is_a?(Project) ? project : Project.find(project)
736 745
737 746 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
738 747 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
739 748
740 749 Project.transaction do
741 750 if save
742 751 reload
743 752 to_be_copied.each do |name|
744 753 send "copy_#{name}", project
745 754 end
746 755 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
747 756 save
748 757 end
749 758 end
750 759 true
751 760 end
752 761
753 762 # Returns a new unsaved Project instance with attributes copied from +project+
754 763 def self.copy_from(project)
755 764 project = project.is_a?(Project) ? project : Project.find(project)
756 765 # clear unique attributes
757 766 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
758 767 copy = Project.new(attributes)
759 768 copy.enabled_modules = project.enabled_modules
760 769 copy.trackers = project.trackers
761 770 copy.custom_values = project.custom_values.collect {|v| v.clone}
762 771 copy.issue_custom_fields = project.issue_custom_fields
763 772 copy
764 773 end
765 774
766 775 # Yields the given block for each project with its level in the tree
767 776 def self.project_tree(projects, &block)
768 777 ancestors = []
769 778 projects.sort_by(&:lft).each do |project|
770 779 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
771 780 ancestors.pop
772 781 end
773 782 yield project, ancestors.size
774 783 ancestors << project
775 784 end
776 785 end
777 786
778 787 private
779 788
780 789 def after_parent_changed(parent_was)
781 790 remove_inherited_member_roles
782 791 add_inherited_member_roles
783 792 end
784 793
785 794 def update_inherited_members
786 795 if parent
787 796 if inherit_members? && !inherit_members_was
788 797 remove_inherited_member_roles
789 798 add_inherited_member_roles
790 799 elsif !inherit_members? && inherit_members_was
791 800 remove_inherited_member_roles
792 801 end
793 802 end
794 803 end
795 804
796 805 def remove_inherited_member_roles
797 806 member_roles = memberships.map(&:member_roles).flatten
798 807 member_role_ids = member_roles.map(&:id)
799 808 member_roles.each do |member_role|
800 809 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
801 810 member_role.destroy
802 811 end
803 812 end
804 813 end
805 814
806 815 def add_inherited_member_roles
807 816 if inherit_members? && parent
808 817 parent.memberships.each do |parent_member|
809 818 member = Member.find_or_new(self.id, parent_member.user_id)
810 819 parent_member.member_roles.each do |parent_member_role|
811 820 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
812 821 end
813 822 member.save!
814 823 end
815 824 end
816 825 end
817 826
818 827 # Copies wiki from +project+
819 828 def copy_wiki(project)
820 829 # Check that the source project has a wiki first
821 830 unless project.wiki.nil?
822 831 wiki = self.wiki || Wiki.new
823 832 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
824 833 wiki_pages_map = {}
825 834 project.wiki.pages.each do |page|
826 835 # Skip pages without content
827 836 next if page.content.nil?
828 837 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
829 838 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
830 839 new_wiki_page.content = new_wiki_content
831 840 wiki.pages << new_wiki_page
832 841 wiki_pages_map[page.id] = new_wiki_page
833 842 end
834 843
835 844 self.wiki = wiki
836 845 wiki.save
837 846 # Reproduce page hierarchy
838 847 project.wiki.pages.each do |page|
839 848 if page.parent_id && wiki_pages_map[page.id]
840 849 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
841 850 wiki_pages_map[page.id].save
842 851 end
843 852 end
844 853 end
845 854 end
846 855
847 856 # Copies versions from +project+
848 857 def copy_versions(project)
849 858 project.versions.each do |version|
850 859 new_version = Version.new
851 860 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
852 861 self.versions << new_version
853 862 end
854 863 end
855 864
856 865 # Copies issue categories from +project+
857 866 def copy_issue_categories(project)
858 867 project.issue_categories.each do |issue_category|
859 868 new_issue_category = IssueCategory.new
860 869 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
861 870 self.issue_categories << new_issue_category
862 871 end
863 872 end
864 873
865 874 # Copies issues from +project+
866 875 def copy_issues(project)
867 876 # Stores the source issue id as a key and the copied issues as the
868 877 # value. Used to map the two together for issue relations.
869 878 issues_map = {}
870 879
871 880 # Store status and reopen locked/closed versions
872 881 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
873 882 version_statuses.each do |version, status|
874 883 version.update_attribute :status, 'open'
875 884 end
876 885
877 886 # Get issues sorted by root_id, lft so that parent issues
878 887 # get copied before their children
879 888 project.issues.reorder('root_id, lft').each do |issue|
880 889 new_issue = Issue.new
881 890 new_issue.copy_from(issue, :subtasks => false, :link => false)
882 891 new_issue.project = self
883 892 # Changing project resets the custom field values
884 893 # TODO: handle this in Issue#project=
885 894 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
886 895 # Reassign fixed_versions by name, since names are unique per project
887 896 if issue.fixed_version && issue.fixed_version.project == project
888 897 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
889 898 end
890 899 # Reassign the category by name, since names are unique per project
891 900 if issue.category
892 901 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
893 902 end
894 903 # Parent issue
895 904 if issue.parent_id
896 905 if copied_parent = issues_map[issue.parent_id]
897 906 new_issue.parent_issue_id = copied_parent.id
898 907 end
899 908 end
900 909
901 910 self.issues << new_issue
902 911 if new_issue.new_record?
903 912 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
904 913 else
905 914 issues_map[issue.id] = new_issue unless new_issue.new_record?
906 915 end
907 916 end
908 917
909 918 # Restore locked/closed version statuses
910 919 version_statuses.each do |version, status|
911 920 version.update_attribute :status, status
912 921 end
913 922
914 923 # Relations after in case issues related each other
915 924 project.issues.each do |issue|
916 925 new_issue = issues_map[issue.id]
917 926 unless new_issue
918 927 # Issue was not copied
919 928 next
920 929 end
921 930
922 931 # Relations
923 932 issue.relations_from.each do |source_relation|
924 933 new_issue_relation = IssueRelation.new
925 934 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
926 935 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
927 936 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
928 937 new_issue_relation.issue_to = source_relation.issue_to
929 938 end
930 939 new_issue.relations_from << new_issue_relation
931 940 end
932 941
933 942 issue.relations_to.each do |source_relation|
934 943 new_issue_relation = IssueRelation.new
935 944 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
936 945 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
937 946 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
938 947 new_issue_relation.issue_from = source_relation.issue_from
939 948 end
940 949 new_issue.relations_to << new_issue_relation
941 950 end
942 951 end
943 952 end
944 953
945 954 # Copies members from +project+
946 955 def copy_members(project)
947 956 # Copy users first, then groups to handle members with inherited and given roles
948 957 members_to_copy = []
949 958 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
950 959 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
951 960
952 961 members_to_copy.each do |member|
953 962 new_member = Member.new
954 963 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
955 964 # only copy non inherited roles
956 965 # inherited roles will be added when copying the group membership
957 966 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
958 967 next if role_ids.empty?
959 968 new_member.role_ids = role_ids
960 969 new_member.project = self
961 970 self.members << new_member
962 971 end
963 972 end
964 973
965 974 # Copies queries from +project+
966 975 def copy_queries(project)
967 976 project.queries.each do |query|
968 977 new_query = IssueQuery.new
969 978 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
970 979 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
971 980 new_query.project = self
972 981 new_query.user_id = query.user_id
973 982 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
974 983 self.queries << new_query
975 984 end
976 985 end
977 986
978 987 # Copies boards from +project+
979 988 def copy_boards(project)
980 989 project.boards.each do |board|
981 990 new_board = Board.new
982 991 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
983 992 new_board.project = self
984 993 self.boards << new_board
985 994 end
986 995 end
987 996
988 997 def allowed_permissions
989 998 @allowed_permissions ||= begin
990 999 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
991 1000 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
992 1001 end
993 1002 end
994 1003
995 1004 def allowed_actions
996 1005 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
997 1006 end
998 1007
999 1008 # Returns all the active Systemwide and project specific activities
1000 1009 def active_activities
1001 1010 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
1002 1011
1003 1012 if overridden_activity_ids.empty?
1004 1013 return TimeEntryActivity.shared.active
1005 1014 else
1006 1015 return system_activities_and_project_overrides
1007 1016 end
1008 1017 end
1009 1018
1010 1019 # Returns all the Systemwide and project specific activities
1011 1020 # (inactive and active)
1012 1021 def all_activities
1013 1022 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
1014 1023
1015 1024 if overridden_activity_ids.empty?
1016 1025 return TimeEntryActivity.shared
1017 1026 else
1018 1027 return system_activities_and_project_overrides(true)
1019 1028 end
1020 1029 end
1021 1030
1022 1031 # Returns the systemwide active activities merged with the project specific overrides
1023 1032 def system_activities_and_project_overrides(include_inactive=false)
1024 1033 t = TimeEntryActivity.table_name
1025 1034 scope = TimeEntryActivity.where(
1026 1035 "(#{t}.project_id IS NULL AND #{t}.id NOT IN (?)) OR (#{t}.project_id = ?)",
1027 1036 time_entry_activities.map(&:parent_id), id
1028 1037 )
1029 1038 unless include_inactive
1030 1039 scope = scope.active
1031 1040 end
1032 1041 scope
1033 1042 end
1034 1043
1035 1044 # Archives subprojects recursively
1036 1045 def archive!
1037 1046 children.each do |subproject|
1038 1047 subproject.send :archive!
1039 1048 end
1040 1049 update_attribute :status, STATUS_ARCHIVED
1041 1050 end
1042 1051
1043 1052 def update_position_under_parent
1044 1053 set_or_update_position_under(parent)
1045 1054 end
1046 1055
1047 1056 public
1048 1057
1049 1058 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted
1050 1059 def set_or_update_position_under(target_parent)
1051 1060 parent_was = parent
1052 1061 sibs = (target_parent.nil? ? self.class.roots : target_parent.children)
1053 1062 to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
1054 1063
1055 1064 if to_be_inserted_before
1056 1065 move_to_left_of(to_be_inserted_before)
1057 1066 elsif target_parent.nil?
1058 1067 if sibs.empty?
1059 1068 # move_to_root adds the project in first (ie. left) position
1060 1069 move_to_root
1061 1070 else
1062 1071 move_to_right_of(sibs.last) unless self == sibs.last
1063 1072 end
1064 1073 else
1065 1074 # move_to_child_of adds the project in last (ie.right) position
1066 1075 move_to_child_of(target_parent)
1067 1076 end
1068 1077 if parent_was != target_parent
1069 1078 after_parent_changed(parent_was)
1070 1079 end
1071 1080 end
1072 1081 end
General Comments 0
You need to be logged in to leave comments. Login now