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