##// END OF EJS Templates
Don't pass conditions to #delete_all....
Jean-Philippe Lang -
r15293:57afa5345eea
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,678 +1,678
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class Unauthorized < Exception; end
21 class Unauthorized < Exception; end
22
22
23 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
24 include Redmine::I18n
24 include Redmine::I18n
25 include Redmine::Pagination
25 include Redmine::Pagination
26 include Redmine::Hook::Helper
26 include Redmine::Hook::Helper
27 include RoutesHelper
27 include RoutesHelper
28 helper :routes
28 helper :routes
29
29
30 class_attribute :accept_api_auth_actions
30 class_attribute :accept_api_auth_actions
31 class_attribute :accept_rss_auth_actions
31 class_attribute :accept_rss_auth_actions
32 class_attribute :model_object
32 class_attribute :model_object
33
33
34 layout 'base'
34 layout 'base'
35
35
36 protect_from_forgery
36 protect_from_forgery
37
37
38 def verify_authenticity_token
38 def verify_authenticity_token
39 unless api_request?
39 unless api_request?
40 super
40 super
41 end
41 end
42 end
42 end
43
43
44 def handle_unverified_request
44 def handle_unverified_request
45 unless api_request?
45 unless api_request?
46 super
46 super
47 cookies.delete(autologin_cookie_name)
47 cookies.delete(autologin_cookie_name)
48 self.logged_user = nil
48 self.logged_user = nil
49 set_localization
49 set_localization
50 render_error :status => 422, :message => "Invalid form authenticity token."
50 render_error :status => 422, :message => "Invalid form authenticity token."
51 end
51 end
52 end
52 end
53
53
54 before_action :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
54 before_action :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
55
55
56 rescue_from ::Unauthorized, :with => :deny_access
56 rescue_from ::Unauthorized, :with => :deny_access
57 rescue_from ::ActionView::MissingTemplate, :with => :missing_template
57 rescue_from ::ActionView::MissingTemplate, :with => :missing_template
58
58
59 include Redmine::Search::Controller
59 include Redmine::Search::Controller
60 include Redmine::MenuManager::MenuController
60 include Redmine::MenuManager::MenuController
61 helper Redmine::MenuManager::MenuHelper
61 helper Redmine::MenuManager::MenuHelper
62
62
63 include Redmine::SudoMode::Controller
63 include Redmine::SudoMode::Controller
64
64
65 def session_expiration
65 def session_expiration
66 if session[:user_id] && Rails.application.config.redmine_verify_sessions != false
66 if session[:user_id] && Rails.application.config.redmine_verify_sessions != false
67 if session_expired? && !try_to_autologin
67 if session_expired? && !try_to_autologin
68 set_localization(User.active.find_by_id(session[:user_id]))
68 set_localization(User.active.find_by_id(session[:user_id]))
69 self.logged_user = nil
69 self.logged_user = nil
70 flash[:error] = l(:error_session_expired)
70 flash[:error] = l(:error_session_expired)
71 require_login
71 require_login
72 end
72 end
73 end
73 end
74 end
74 end
75
75
76 def session_expired?
76 def session_expired?
77 ! User.verify_session_token(session[:user_id], session[:tk])
77 ! User.verify_session_token(session[:user_id], session[:tk])
78 end
78 end
79
79
80 def start_user_session(user)
80 def start_user_session(user)
81 session[:user_id] = user.id
81 session[:user_id] = user.id
82 session[:tk] = user.generate_session_token
82 session[:tk] = user.generate_session_token
83 if user.must_change_password?
83 if user.must_change_password?
84 session[:pwd] = '1'
84 session[:pwd] = '1'
85 end
85 end
86 end
86 end
87
87
88 def user_setup
88 def user_setup
89 # Check the settings cache for each request
89 # Check the settings cache for each request
90 Setting.check_cache
90 Setting.check_cache
91 # Find the current user
91 # Find the current user
92 User.current = find_current_user
92 User.current = find_current_user
93 logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
93 logger.info(" Current user: " + (User.current.logged? ? "#{User.current.login} (id=#{User.current.id})" : "anonymous")) if logger
94 end
94 end
95
95
96 # Returns the current user or nil if no user is logged in
96 # Returns the current user or nil if no user is logged in
97 # and starts a session if needed
97 # and starts a session if needed
98 def find_current_user
98 def find_current_user
99 user = nil
99 user = nil
100 unless api_request?
100 unless api_request?
101 if session[:user_id]
101 if session[:user_id]
102 # existing session
102 # existing session
103 user = (User.active.find(session[:user_id]) rescue nil)
103 user = (User.active.find(session[:user_id]) rescue nil)
104 elsif autologin_user = try_to_autologin
104 elsif autologin_user = try_to_autologin
105 user = autologin_user
105 user = autologin_user
106 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
106 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
107 # RSS key authentication does not start a session
107 # RSS key authentication does not start a session
108 user = User.find_by_rss_key(params[:key])
108 user = User.find_by_rss_key(params[:key])
109 end
109 end
110 end
110 end
111 if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
111 if user.nil? && Setting.rest_api_enabled? && accept_api_auth?
112 if (key = api_key_from_request)
112 if (key = api_key_from_request)
113 # Use API key
113 # Use API key
114 user = User.find_by_api_key(key)
114 user = User.find_by_api_key(key)
115 elsif request.authorization.to_s =~ /\ABasic /i
115 elsif request.authorization.to_s =~ /\ABasic /i
116 # HTTP Basic, either username/password or API key/random
116 # HTTP Basic, either username/password or API key/random
117 authenticate_with_http_basic do |username, password|
117 authenticate_with_http_basic do |username, password|
118 user = User.try_to_login(username, password) || User.find_by_api_key(username)
118 user = User.try_to_login(username, password) || User.find_by_api_key(username)
119 end
119 end
120 if user && user.must_change_password?
120 if user && user.must_change_password?
121 render_error :message => 'You must change your password', :status => 403
121 render_error :message => 'You must change your password', :status => 403
122 return
122 return
123 end
123 end
124 end
124 end
125 # Switch user if requested by an admin user
125 # Switch user if requested by an admin user
126 if user && user.admin? && (username = api_switch_user_from_request)
126 if user && user.admin? && (username = api_switch_user_from_request)
127 su = User.find_by_login(username)
127 su = User.find_by_login(username)
128 if su && su.active?
128 if su && su.active?
129 logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
129 logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
130 user = su
130 user = su
131 else
131 else
132 render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
132 render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
133 end
133 end
134 end
134 end
135 end
135 end
136 # store current ip address in user object ephemerally
136 # store current ip address in user object ephemerally
137 user.remote_ip = request.remote_ip if user
137 user.remote_ip = request.remote_ip if user
138 user
138 user
139 end
139 end
140
140
141 def autologin_cookie_name
141 def autologin_cookie_name
142 Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
142 Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
143 end
143 end
144
144
145 def try_to_autologin
145 def try_to_autologin
146 if cookies[autologin_cookie_name] && Setting.autologin?
146 if cookies[autologin_cookie_name] && Setting.autologin?
147 # auto-login feature starts a new session
147 # auto-login feature starts a new session
148 user = User.try_to_autologin(cookies[autologin_cookie_name])
148 user = User.try_to_autologin(cookies[autologin_cookie_name])
149 if user
149 if user
150 reset_session
150 reset_session
151 start_user_session(user)
151 start_user_session(user)
152 end
152 end
153 user
153 user
154 end
154 end
155 end
155 end
156
156
157 # Sets the logged in user
157 # Sets the logged in user
158 def logged_user=(user)
158 def logged_user=(user)
159 reset_session
159 reset_session
160 if user && user.is_a?(User)
160 if user && user.is_a?(User)
161 User.current = user
161 User.current = user
162 start_user_session(user)
162 start_user_session(user)
163 else
163 else
164 User.current = User.anonymous
164 User.current = User.anonymous
165 end
165 end
166 end
166 end
167
167
168 # Logs out current user
168 # Logs out current user
169 def logout_user
169 def logout_user
170 if User.current.logged?
170 if User.current.logged?
171 cookies.delete(autologin_cookie_name)
171 cookies.delete(autologin_cookie_name)
172 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
172 Token.where(["user_id = ? AND action = ?", User.current.id, 'autologin']).delete_all
173 Token.delete_all(["user_id = ? AND action = ? AND value = ?", User.current.id, 'session', session[:tk]])
173 Token.where(["user_id = ? AND action = ? AND value = ?", User.current.id, 'session', session[:tk]]).delete_all
174 self.logged_user = nil
174 self.logged_user = nil
175 end
175 end
176 end
176 end
177
177
178 # check if login is globally required to access the application
178 # check if login is globally required to access the application
179 def check_if_login_required
179 def check_if_login_required
180 # no check needed if user is already logged in
180 # no check needed if user is already logged in
181 return true if User.current.logged?
181 return true if User.current.logged?
182 require_login if Setting.login_required?
182 require_login if Setting.login_required?
183 end
183 end
184
184
185 def check_password_change
185 def check_password_change
186 if session[:pwd]
186 if session[:pwd]
187 if User.current.must_change_password?
187 if User.current.must_change_password?
188 flash[:error] = l(:error_password_expired)
188 flash[:error] = l(:error_password_expired)
189 redirect_to my_password_path
189 redirect_to my_password_path
190 else
190 else
191 session.delete(:pwd)
191 session.delete(:pwd)
192 end
192 end
193 end
193 end
194 end
194 end
195
195
196 def set_localization(user=User.current)
196 def set_localization(user=User.current)
197 lang = nil
197 lang = nil
198 if user && user.logged?
198 if user && user.logged?
199 lang = find_language(user.language)
199 lang = find_language(user.language)
200 end
200 end
201 if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE']
201 if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE']
202 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
202 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
203 if !accept_lang.blank?
203 if !accept_lang.blank?
204 accept_lang = accept_lang.downcase
204 accept_lang = accept_lang.downcase
205 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
205 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
206 end
206 end
207 end
207 end
208 lang ||= Setting.default_language
208 lang ||= Setting.default_language
209 set_language_if_valid(lang)
209 set_language_if_valid(lang)
210 end
210 end
211
211
212 def require_login
212 def require_login
213 if !User.current.logged?
213 if !User.current.logged?
214 # Extract only the basic url parameters on non-GET requests
214 # Extract only the basic url parameters on non-GET requests
215 if request.get?
215 if request.get?
216 url = request.original_url
216 url = request.original_url
217 else
217 else
218 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
218 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
219 end
219 end
220 respond_to do |format|
220 respond_to do |format|
221 format.html {
221 format.html {
222 if request.xhr?
222 if request.xhr?
223 head :unauthorized
223 head :unauthorized
224 else
224 else
225 redirect_to signin_path(:back_url => url)
225 redirect_to signin_path(:back_url => url)
226 end
226 end
227 }
227 }
228 format.any(:atom, :pdf, :csv) {
228 format.any(:atom, :pdf, :csv) {
229 redirect_to signin_path(:back_url => url)
229 redirect_to signin_path(:back_url => url)
230 }
230 }
231 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
231 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
232 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
232 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
233 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
233 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
234 format.any { head :unauthorized }
234 format.any { head :unauthorized }
235 end
235 end
236 return false
236 return false
237 end
237 end
238 true
238 true
239 end
239 end
240
240
241 def require_admin
241 def require_admin
242 return unless require_login
242 return unless require_login
243 if !User.current.admin?
243 if !User.current.admin?
244 render_403
244 render_403
245 return false
245 return false
246 end
246 end
247 true
247 true
248 end
248 end
249
249
250 def deny_access
250 def deny_access
251 User.current.logged? ? render_403 : require_login
251 User.current.logged? ? render_403 : require_login
252 end
252 end
253
253
254 # Authorize the user for the requested action
254 # Authorize the user for the requested action
255 def authorize(ctrl = params[:controller], action = params[:action], global = false)
255 def authorize(ctrl = params[:controller], action = params[:action], global = false)
256 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
256 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
257 if allowed
257 if allowed
258 true
258 true
259 else
259 else
260 if @project && @project.archived?
260 if @project && @project.archived?
261 render_403 :message => :notice_not_authorized_archived_project
261 render_403 :message => :notice_not_authorized_archived_project
262 else
262 else
263 deny_access
263 deny_access
264 end
264 end
265 end
265 end
266 end
266 end
267
267
268 # Authorize the user for the requested action outside a project
268 # Authorize the user for the requested action outside a project
269 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
269 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
270 authorize(ctrl, action, global)
270 authorize(ctrl, action, global)
271 end
271 end
272
272
273 # Find project of id params[:id]
273 # Find project of id params[:id]
274 def find_project
274 def find_project
275 @project = Project.find(params[:id])
275 @project = Project.find(params[:id])
276 rescue ActiveRecord::RecordNotFound
276 rescue ActiveRecord::RecordNotFound
277 render_404
277 render_404
278 end
278 end
279
279
280 # Find project of id params[:project_id]
280 # Find project of id params[:project_id]
281 def find_project_by_project_id
281 def find_project_by_project_id
282 @project = Project.find(params[:project_id])
282 @project = Project.find(params[:project_id])
283 rescue ActiveRecord::RecordNotFound
283 rescue ActiveRecord::RecordNotFound
284 render_404
284 render_404
285 end
285 end
286
286
287 # Find a project based on params[:project_id]
287 # Find a project based on params[:project_id]
288 # TODO: some subclasses override this, see about merging their logic
288 # TODO: some subclasses override this, see about merging their logic
289 def find_optional_project
289 def find_optional_project
290 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
290 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
291 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
291 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
292 allowed ? true : deny_access
292 allowed ? true : deny_access
293 rescue ActiveRecord::RecordNotFound
293 rescue ActiveRecord::RecordNotFound
294 render_404
294 render_404
295 end
295 end
296
296
297 # Finds and sets @project based on @object.project
297 # Finds and sets @project based on @object.project
298 def find_project_from_association
298 def find_project_from_association
299 render_404 unless @object.present?
299 render_404 unless @object.present?
300
300
301 @project = @object.project
301 @project = @object.project
302 end
302 end
303
303
304 def find_model_object
304 def find_model_object
305 model = self.class.model_object
305 model = self.class.model_object
306 if model
306 if model
307 @object = model.find(params[:id])
307 @object = model.find(params[:id])
308 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
308 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
309 end
309 end
310 rescue ActiveRecord::RecordNotFound
310 rescue ActiveRecord::RecordNotFound
311 render_404
311 render_404
312 end
312 end
313
313
314 def self.model_object(model)
314 def self.model_object(model)
315 self.model_object = model
315 self.model_object = model
316 end
316 end
317
317
318 # Find the issue whose id is the :id parameter
318 # Find the issue whose id is the :id parameter
319 # Raises a Unauthorized exception if the issue is not visible
319 # Raises a Unauthorized exception if the issue is not visible
320 def find_issue
320 def find_issue
321 # Issue.visible.find(...) can not be used to redirect user to the login form
321 # Issue.visible.find(...) can not be used to redirect user to the login form
322 # if the issue actually exists but requires authentication
322 # if the issue actually exists but requires authentication
323 @issue = Issue.find(params[:id])
323 @issue = Issue.find(params[:id])
324 raise Unauthorized unless @issue.visible?
324 raise Unauthorized unless @issue.visible?
325 @project = @issue.project
325 @project = @issue.project
326 rescue ActiveRecord::RecordNotFound
326 rescue ActiveRecord::RecordNotFound
327 render_404
327 render_404
328 end
328 end
329
329
330 # Find issues with a single :id param or :ids array param
330 # Find issues with a single :id param or :ids array param
331 # Raises a Unauthorized exception if one of the issues is not visible
331 # Raises a Unauthorized exception if one of the issues is not visible
332 def find_issues
332 def find_issues
333 @issues = Issue.
333 @issues = Issue.
334 where(:id => (params[:id] || params[:ids])).
334 where(:id => (params[:id] || params[:ids])).
335 preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to, {:custom_values => :custom_field}).
335 preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to, {:custom_values => :custom_field}).
336 to_a
336 to_a
337 raise ActiveRecord::RecordNotFound if @issues.empty?
337 raise ActiveRecord::RecordNotFound if @issues.empty?
338 raise Unauthorized unless @issues.all?(&:visible?)
338 raise Unauthorized unless @issues.all?(&:visible?)
339 @projects = @issues.collect(&:project).compact.uniq
339 @projects = @issues.collect(&:project).compact.uniq
340 @project = @projects.first if @projects.size == 1
340 @project = @projects.first if @projects.size == 1
341 rescue ActiveRecord::RecordNotFound
341 rescue ActiveRecord::RecordNotFound
342 render_404
342 render_404
343 end
343 end
344
344
345 def find_attachments
345 def find_attachments
346 if (attachments = params[:attachments]).present?
346 if (attachments = params[:attachments]).present?
347 att = attachments.values.collect do |attachment|
347 att = attachments.values.collect do |attachment|
348 Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
348 Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
349 end
349 end
350 att.compact!
350 att.compact!
351 end
351 end
352 @attachments = att || []
352 @attachments = att || []
353 end
353 end
354
354
355 def parse_params_for_bulk_update(params)
355 def parse_params_for_bulk_update(params)
356 attributes = (params || {}).reject {|k,v| v.blank?}
356 attributes = (params || {}).reject {|k,v| v.blank?}
357 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
357 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
358 if custom = attributes[:custom_field_values]
358 if custom = attributes[:custom_field_values]
359 custom.reject! {|k,v| v.blank?}
359 custom.reject! {|k,v| v.blank?}
360 custom.keys.each do |k|
360 custom.keys.each do |k|
361 if custom[k].is_a?(Array)
361 if custom[k].is_a?(Array)
362 custom[k] << '' if custom[k].delete('__none__')
362 custom[k] << '' if custom[k].delete('__none__')
363 else
363 else
364 custom[k] = '' if custom[k] == '__none__'
364 custom[k] = '' if custom[k] == '__none__'
365 end
365 end
366 end
366 end
367 end
367 end
368 attributes
368 attributes
369 end
369 end
370
370
371 # make sure that the user is a member of the project (or admin) if project is private
371 # make sure that the user is a member of the project (or admin) if project is private
372 # used as a before_action for actions that do not require any particular permission on the project
372 # used as a before_action for actions that do not require any particular permission on the project
373 def check_project_privacy
373 def check_project_privacy
374 if @project && !@project.archived?
374 if @project && !@project.archived?
375 if @project.visible?
375 if @project.visible?
376 true
376 true
377 else
377 else
378 deny_access
378 deny_access
379 end
379 end
380 else
380 else
381 @project = nil
381 @project = nil
382 render_404
382 render_404
383 false
383 false
384 end
384 end
385 end
385 end
386
386
387 def back_url
387 def back_url
388 url = params[:back_url]
388 url = params[:back_url]
389 if url.nil? && referer = request.env['HTTP_REFERER']
389 if url.nil? && referer = request.env['HTTP_REFERER']
390 url = CGI.unescape(referer.to_s)
390 url = CGI.unescape(referer.to_s)
391 end
391 end
392 url
392 url
393 end
393 end
394
394
395 def redirect_back_or_default(default, options={})
395 def redirect_back_or_default(default, options={})
396 back_url = params[:back_url].to_s
396 back_url = params[:back_url].to_s
397 if back_url.present? && valid_url = validate_back_url(back_url)
397 if back_url.present? && valid_url = validate_back_url(back_url)
398 redirect_to(valid_url)
398 redirect_to(valid_url)
399 return
399 return
400 elsif options[:referer]
400 elsif options[:referer]
401 redirect_to_referer_or default
401 redirect_to_referer_or default
402 return
402 return
403 end
403 end
404 redirect_to default
404 redirect_to default
405 false
405 false
406 end
406 end
407
407
408 # Returns a validated URL string if back_url is a valid url for redirection,
408 # Returns a validated URL string if back_url is a valid url for redirection,
409 # otherwise false
409 # otherwise false
410 def validate_back_url(back_url)
410 def validate_back_url(back_url)
411 if CGI.unescape(back_url).include?('..')
411 if CGI.unescape(back_url).include?('..')
412 return false
412 return false
413 end
413 end
414
414
415 begin
415 begin
416 uri = URI.parse(back_url)
416 uri = URI.parse(back_url)
417 rescue URI::InvalidURIError
417 rescue URI::InvalidURIError
418 return false
418 return false
419 end
419 end
420
420
421 [:scheme, :host, :port].each do |component|
421 [:scheme, :host, :port].each do |component|
422 if uri.send(component).present? && uri.send(component) != request.send(component)
422 if uri.send(component).present? && uri.send(component) != request.send(component)
423 return false
423 return false
424 end
424 end
425 uri.send(:"#{component}=", nil)
425 uri.send(:"#{component}=", nil)
426 end
426 end
427 # Always ignore basic user:password in the URL
427 # Always ignore basic user:password in the URL
428 uri.userinfo = nil
428 uri.userinfo = nil
429
429
430 path = uri.to_s
430 path = uri.to_s
431 # Ensure that the remaining URL starts with a slash, followed by a
431 # Ensure that the remaining URL starts with a slash, followed by a
432 # non-slash character or the end
432 # non-slash character or the end
433 if path !~ %r{\A/([^/]|\z)}
433 if path !~ %r{\A/([^/]|\z)}
434 return false
434 return false
435 end
435 end
436
436
437 if path.match(%r{/(login|account/register)})
437 if path.match(%r{/(login|account/register)})
438 return false
438 return false
439 end
439 end
440
440
441 if relative_url_root.present? && !path.starts_with?(relative_url_root)
441 if relative_url_root.present? && !path.starts_with?(relative_url_root)
442 return false
442 return false
443 end
443 end
444
444
445 return path
445 return path
446 end
446 end
447 private :validate_back_url
447 private :validate_back_url
448
448
449 def valid_back_url?(back_url)
449 def valid_back_url?(back_url)
450 !!validate_back_url(back_url)
450 !!validate_back_url(back_url)
451 end
451 end
452 private :valid_back_url?
452 private :valid_back_url?
453
453
454 # Redirects to the request referer if present, redirects to args or call block otherwise.
454 # Redirects to the request referer if present, redirects to args or call block otherwise.
455 def redirect_to_referer_or(*args, &block)
455 def redirect_to_referer_or(*args, &block)
456 redirect_to :back
456 redirect_to :back
457 rescue ::ActionController::RedirectBackError
457 rescue ::ActionController::RedirectBackError
458 if args.any?
458 if args.any?
459 redirect_to *args
459 redirect_to *args
460 elsif block_given?
460 elsif block_given?
461 block.call
461 block.call
462 else
462 else
463 raise "#redirect_to_referer_or takes arguments or a block"
463 raise "#redirect_to_referer_or takes arguments or a block"
464 end
464 end
465 end
465 end
466
466
467 def render_403(options={})
467 def render_403(options={})
468 @project = nil
468 @project = nil
469 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
469 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
470 return false
470 return false
471 end
471 end
472
472
473 def render_404(options={})
473 def render_404(options={})
474 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
474 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
475 return false
475 return false
476 end
476 end
477
477
478 # Renders an error response
478 # Renders an error response
479 def render_error(arg)
479 def render_error(arg)
480 arg = {:message => arg} unless arg.is_a?(Hash)
480 arg = {:message => arg} unless arg.is_a?(Hash)
481
481
482 @message = arg[:message]
482 @message = arg[:message]
483 @message = l(@message) if @message.is_a?(Symbol)
483 @message = l(@message) if @message.is_a?(Symbol)
484 @status = arg[:status] || 500
484 @status = arg[:status] || 500
485
485
486 respond_to do |format|
486 respond_to do |format|
487 format.html {
487 format.html {
488 render :template => 'common/error', :layout => use_layout, :status => @status
488 render :template => 'common/error', :layout => use_layout, :status => @status
489 }
489 }
490 format.any { head @status }
490 format.any { head @status }
491 end
491 end
492 end
492 end
493
493
494 # Handler for ActionView::MissingTemplate exception
494 # Handler for ActionView::MissingTemplate exception
495 def missing_template
495 def missing_template
496 logger.warn "Missing template, responding with 404"
496 logger.warn "Missing template, responding with 404"
497 @project = nil
497 @project = nil
498 render_404
498 render_404
499 end
499 end
500
500
501 # Filter for actions that provide an API response
501 # Filter for actions that provide an API response
502 # but have no HTML representation for non admin users
502 # but have no HTML representation for non admin users
503 def require_admin_or_api_request
503 def require_admin_or_api_request
504 return true if api_request?
504 return true if api_request?
505 if User.current.admin?
505 if User.current.admin?
506 true
506 true
507 elsif User.current.logged?
507 elsif User.current.logged?
508 render_error(:status => 406)
508 render_error(:status => 406)
509 else
509 else
510 deny_access
510 deny_access
511 end
511 end
512 end
512 end
513
513
514 # Picks which layout to use based on the request
514 # Picks which layout to use based on the request
515 #
515 #
516 # @return [boolean, string] name of the layout to use or false for no layout
516 # @return [boolean, string] name of the layout to use or false for no layout
517 def use_layout
517 def use_layout
518 request.xhr? ? false : 'base'
518 request.xhr? ? false : 'base'
519 end
519 end
520
520
521 def render_feed(items, options={})
521 def render_feed(items, options={})
522 @items = (items || []).to_a
522 @items = (items || []).to_a
523 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
523 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
524 @items = @items.slice(0, Setting.feeds_limit.to_i)
524 @items = @items.slice(0, Setting.feeds_limit.to_i)
525 @title = options[:title] || Setting.app_title
525 @title = options[:title] || Setting.app_title
526 render :template => "common/feed", :formats => [:atom], :layout => false,
526 render :template => "common/feed", :formats => [:atom], :layout => false,
527 :content_type => 'application/atom+xml'
527 :content_type => 'application/atom+xml'
528 end
528 end
529
529
530 def self.accept_rss_auth(*actions)
530 def self.accept_rss_auth(*actions)
531 if actions.any?
531 if actions.any?
532 self.accept_rss_auth_actions = actions
532 self.accept_rss_auth_actions = actions
533 else
533 else
534 self.accept_rss_auth_actions || []
534 self.accept_rss_auth_actions || []
535 end
535 end
536 end
536 end
537
537
538 def accept_rss_auth?(action=action_name)
538 def accept_rss_auth?(action=action_name)
539 self.class.accept_rss_auth.include?(action.to_sym)
539 self.class.accept_rss_auth.include?(action.to_sym)
540 end
540 end
541
541
542 def self.accept_api_auth(*actions)
542 def self.accept_api_auth(*actions)
543 if actions.any?
543 if actions.any?
544 self.accept_api_auth_actions = actions
544 self.accept_api_auth_actions = actions
545 else
545 else
546 self.accept_api_auth_actions || []
546 self.accept_api_auth_actions || []
547 end
547 end
548 end
548 end
549
549
550 def accept_api_auth?(action=action_name)
550 def accept_api_auth?(action=action_name)
551 self.class.accept_api_auth.include?(action.to_sym)
551 self.class.accept_api_auth.include?(action.to_sym)
552 end
552 end
553
553
554 # Returns the number of objects that should be displayed
554 # Returns the number of objects that should be displayed
555 # on the paginated list
555 # on the paginated list
556 def per_page_option
556 def per_page_option
557 per_page = nil
557 per_page = nil
558 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
558 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
559 per_page = params[:per_page].to_s.to_i
559 per_page = params[:per_page].to_s.to_i
560 session[:per_page] = per_page
560 session[:per_page] = per_page
561 elsif session[:per_page]
561 elsif session[:per_page]
562 per_page = session[:per_page]
562 per_page = session[:per_page]
563 else
563 else
564 per_page = Setting.per_page_options_array.first || 25
564 per_page = Setting.per_page_options_array.first || 25
565 end
565 end
566 per_page
566 per_page
567 end
567 end
568
568
569 # Returns offset and limit used to retrieve objects
569 # Returns offset and limit used to retrieve objects
570 # for an API response based on offset, limit and page parameters
570 # for an API response based on offset, limit and page parameters
571 def api_offset_and_limit(options=params)
571 def api_offset_and_limit(options=params)
572 if options[:offset].present?
572 if options[:offset].present?
573 offset = options[:offset].to_i
573 offset = options[:offset].to_i
574 if offset < 0
574 if offset < 0
575 offset = 0
575 offset = 0
576 end
576 end
577 end
577 end
578 limit = options[:limit].to_i
578 limit = options[:limit].to_i
579 if limit < 1
579 if limit < 1
580 limit = 25
580 limit = 25
581 elsif limit > 100
581 elsif limit > 100
582 limit = 100
582 limit = 100
583 end
583 end
584 if offset.nil? && options[:page].present?
584 if offset.nil? && options[:page].present?
585 offset = (options[:page].to_i - 1) * limit
585 offset = (options[:page].to_i - 1) * limit
586 offset = 0 if offset < 0
586 offset = 0 if offset < 0
587 end
587 end
588 offset ||= 0
588 offset ||= 0
589
589
590 [offset, limit]
590 [offset, limit]
591 end
591 end
592
592
593 # qvalues http header parser
593 # qvalues http header parser
594 # code taken from webrick
594 # code taken from webrick
595 def parse_qvalues(value)
595 def parse_qvalues(value)
596 tmp = []
596 tmp = []
597 if value
597 if value
598 parts = value.split(/,\s*/)
598 parts = value.split(/,\s*/)
599 parts.each {|part|
599 parts.each {|part|
600 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
600 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
601 val = m[1]
601 val = m[1]
602 q = (m[2] or 1).to_f
602 q = (m[2] or 1).to_f
603 tmp.push([val, q])
603 tmp.push([val, q])
604 end
604 end
605 }
605 }
606 tmp = tmp.sort_by{|val, q| -q}
606 tmp = tmp.sort_by{|val, q| -q}
607 tmp.collect!{|val, q| val}
607 tmp.collect!{|val, q| val}
608 end
608 end
609 return tmp
609 return tmp
610 rescue
610 rescue
611 nil
611 nil
612 end
612 end
613
613
614 # Returns a string that can be used as filename value in Content-Disposition header
614 # Returns a string that can be used as filename value in Content-Disposition header
615 def filename_for_content_disposition(name)
615 def filename_for_content_disposition(name)
616 request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident|Edge)} ? ERB::Util.url_encode(name) : name
616 request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident|Edge)} ? ERB::Util.url_encode(name) : name
617 end
617 end
618
618
619 def api_request?
619 def api_request?
620 %w(xml json).include? params[:format]
620 %w(xml json).include? params[:format]
621 end
621 end
622
622
623 # Returns the API key present in the request
623 # Returns the API key present in the request
624 def api_key_from_request
624 def api_key_from_request
625 if params[:key].present?
625 if params[:key].present?
626 params[:key].to_s
626 params[:key].to_s
627 elsif request.headers["X-Redmine-API-Key"].present?
627 elsif request.headers["X-Redmine-API-Key"].present?
628 request.headers["X-Redmine-API-Key"].to_s
628 request.headers["X-Redmine-API-Key"].to_s
629 end
629 end
630 end
630 end
631
631
632 # Returns the API 'switch user' value if present
632 # Returns the API 'switch user' value if present
633 def api_switch_user_from_request
633 def api_switch_user_from_request
634 request.headers["X-Redmine-Switch-User"].to_s.presence
634 request.headers["X-Redmine-Switch-User"].to_s.presence
635 end
635 end
636
636
637 # Renders a warning flash if obj has unsaved attachments
637 # Renders a warning flash if obj has unsaved attachments
638 def render_attachment_warning_if_needed(obj)
638 def render_attachment_warning_if_needed(obj)
639 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
639 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
640 end
640 end
641
641
642 # Rescues an invalid query statement. Just in case...
642 # Rescues an invalid query statement. Just in case...
643 def query_statement_invalid(exception)
643 def query_statement_invalid(exception)
644 logger.error "Query::StatementInvalid: #{exception.message}" if logger
644 logger.error "Query::StatementInvalid: #{exception.message}" if logger
645 session.delete(:query)
645 session.delete(:query)
646 sort_clear if respond_to?(:sort_clear)
646 sort_clear if respond_to?(:sort_clear)
647 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
647 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
648 end
648 end
649
649
650 # Renders a 200 response for successfull updates or deletions via the API
650 # Renders a 200 response for successfull updates or deletions via the API
651 def render_api_ok
651 def render_api_ok
652 render_api_head :ok
652 render_api_head :ok
653 end
653 end
654
654
655 # Renders a head API response
655 # Renders a head API response
656 def render_api_head(status)
656 def render_api_head(status)
657 # #head would return a response body with one space
657 # #head would return a response body with one space
658 render :text => '', :status => status, :layout => nil
658 render :text => '', :status => status, :layout => nil
659 end
659 end
660
660
661 # Renders API response on validation failure
661 # Renders API response on validation failure
662 # for an object or an array of objects
662 # for an object or an array of objects
663 def render_validation_errors(objects)
663 def render_validation_errors(objects)
664 messages = Array.wrap(objects).map {|object| object.errors.full_messages}.flatten
664 messages = Array.wrap(objects).map {|object| object.errors.full_messages}.flatten
665 render_api_errors(messages)
665 render_api_errors(messages)
666 end
666 end
667
667
668 def render_api_errors(*messages)
668 def render_api_errors(*messages)
669 @error_messages = messages.flatten
669 @error_messages = messages.flatten
670 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
670 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
671 end
671 end
672
672
673 # Overrides #_include_layout? so that #render with no arguments
673 # Overrides #_include_layout? so that #render with no arguments
674 # doesn't use the layout for api requests
674 # doesn't use the layout for api requests
675 def _include_layout?(*args)
675 def _include_layout?(*args)
676 api_request? ? false : super
676 api_request? ? false : super
677 end
677 end
678 end
678 end
@@ -1,113 +1,113
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 IssueStatus < ActiveRecord::Base
18 class IssueStatus < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
20 has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
21 has_many :workflow_transitions_as_new_status, :class_name => 'WorkflowTransition', :foreign_key => "new_status_id"
21 has_many :workflow_transitions_as_new_status, :class_name => 'WorkflowTransition', :foreign_key => "new_status_id"
22 acts_as_positioned
22 acts_as_positioned
23
23
24 after_update :handle_is_closed_change
24 after_update :handle_is_closed_change
25 before_destroy :delete_workflow_rules
25 before_destroy :delete_workflow_rules
26
26
27 validates_presence_of :name
27 validates_presence_of :name
28 validates_uniqueness_of :name
28 validates_uniqueness_of :name
29 validates_length_of :name, :maximum => 30
29 validates_length_of :name, :maximum => 30
30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
31 attr_protected :id
31 attr_protected :id
32
32
33 scope :sorted, lambda { order(:position) }
33 scope :sorted, lambda { order(:position) }
34 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
34 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
35
35
36 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
36 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
37 def self.update_issue_done_ratios
37 def self.update_issue_done_ratios
38 if Issue.use_status_for_done_ratio?
38 if Issue.use_status_for_done_ratio?
39 IssueStatus.where("default_done_ratio >= 0").each do |status|
39 IssueStatus.where("default_done_ratio >= 0").each do |status|
40 Issue.where({:status_id => status.id}).update_all({:done_ratio => status.default_done_ratio})
40 Issue.where({:status_id => status.id}).update_all({:done_ratio => status.default_done_ratio})
41 end
41 end
42 end
42 end
43
43
44 return Issue.use_status_for_done_ratio?
44 return Issue.use_status_for_done_ratio?
45 end
45 end
46
46
47 # Returns an array of all statuses the given role can switch to
47 # Returns an array of all statuses the given role can switch to
48 def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
48 def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
49 self.class.new_statuses_allowed(self, roles, tracker, author, assignee)
49 self.class.new_statuses_allowed(self, roles, tracker, author, assignee)
50 end
50 end
51 alias :find_new_statuses_allowed_to :new_statuses_allowed_to
51 alias :find_new_statuses_allowed_to :new_statuses_allowed_to
52
52
53 def self.new_statuses_allowed(status, roles, tracker, author=false, assignee=false)
53 def self.new_statuses_allowed(status, roles, tracker, author=false, assignee=false)
54 if roles.present? && tracker
54 if roles.present? && tracker
55 status_id = status.try(:id) || 0
55 status_id = status.try(:id) || 0
56
56
57 scope = IssueStatus.
57 scope = IssueStatus.
58 joins(:workflow_transitions_as_new_status).
58 joins(:workflow_transitions_as_new_status).
59 where(:workflows => {:old_status_id => status_id, :role_id => roles.map(&:id), :tracker_id => tracker.id})
59 where(:workflows => {:old_status_id => status_id, :role_id => roles.map(&:id), :tracker_id => tracker.id})
60
60
61 unless author && assignee
61 unless author && assignee
62 if author || assignee
62 if author || assignee
63 scope = scope.where("author = ? OR assignee = ?", author, assignee)
63 scope = scope.where("author = ? OR assignee = ?", author, assignee)
64 else
64 else
65 scope = scope.where("author = ? AND assignee = ?", false, false)
65 scope = scope.where("author = ? AND assignee = ?", false, false)
66 end
66 end
67 end
67 end
68
68
69 scope.distinct.to_a.sort
69 scope.distinct.to_a.sort
70 else
70 else
71 []
71 []
72 end
72 end
73 end
73 end
74
74
75 def <=>(status)
75 def <=>(status)
76 position <=> status.position
76 position <=> status.position
77 end
77 end
78
78
79 def to_s; name end
79 def to_s; name end
80
80
81 private
81 private
82
82
83 # Updates issues closed_on attribute when an existing status is set as closed.
83 # Updates issues closed_on attribute when an existing status is set as closed.
84 def handle_is_closed_change
84 def handle_is_closed_change
85 if is_closed_changed? && is_closed == true
85 if is_closed_changed? && is_closed == true
86 # First we update issues that have a journal for when the current status was set,
86 # First we update issues that have a journal for when the current status was set,
87 # a subselect is used to update all issues with a single query
87 # a subselect is used to update all issues with a single query
88 subselect = "SELECT MAX(j.created_on) FROM #{Journal.table_name} j" +
88 subselect = "SELECT MAX(j.created_on) FROM #{Journal.table_name} j" +
89 " JOIN #{JournalDetail.table_name} d ON d.journal_id = j.id" +
89 " JOIN #{JournalDetail.table_name} d ON d.journal_id = j.id" +
90 " WHERE j.journalized_type = 'Issue' AND j.journalized_id = #{Issue.table_name}.id" +
90 " WHERE j.journalized_type = 'Issue' AND j.journalized_id = #{Issue.table_name}.id" +
91 " AND d.property = 'attr' AND d.prop_key = 'status_id' AND d.value = :status_id"
91 " AND d.property = 'attr' AND d.prop_key = 'status_id' AND d.value = :status_id"
92 Issue.where(:status_id => id, :closed_on => nil).
92 Issue.where(:status_id => id, :closed_on => nil).
93 update_all(["closed_on = (#{subselect})", {:status_id => id.to_s}])
93 update_all(["closed_on = (#{subselect})", {:status_id => id.to_s}])
94
94
95 # Then we update issues that don't have a journal which means the
95 # Then we update issues that don't have a journal which means the
96 # current status was set on creation
96 # current status was set on creation
97 Issue.where(:status_id => id, :closed_on => nil).update_all("closed_on = created_on")
97 Issue.where(:status_id => id, :closed_on => nil).update_all("closed_on = created_on")
98 end
98 end
99 end
99 end
100
100
101 def check_integrity
101 def check_integrity
102 if Issue.where(:status_id => id).any?
102 if Issue.where(:status_id => id).any?
103 raise "This status is used by some issues"
103 raise "This status is used by some issues"
104 elsif Tracker.where(:default_status_id => id).any?
104 elsif Tracker.where(:default_status_id => id).any?
105 raise "This status is used as the default status by some trackers"
105 raise "This status is used as the default status by some trackers"
106 end
106 end
107 end
107 end
108
108
109 # Deletes associated workflows
109 # Deletes associated workflows
110 def delete_workflow_rules
110 def delete_workflow_rules
111 WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
111 WorkflowRule.where(["old_status_id = :id OR new_status_id = :id", {:id => id}]).delete_all
112 end
112 end
113 end
113 end
@@ -1,1066 +1,1066
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 :memberships, :class_name => 'Member', :inverse_of => :project
32 has_many :memberships, :class_name => 'Member', :inverse_of => :project
33 # Memberships of active users only
33 # Memberships of active users only
34 has_many :members,
34 has_many :members,
35 lambda { joins(:principal).where(:users => {:type => 'User', :status => Principal::STATUS_ACTIVE}) }
35 lambda { joins(:principal).where(:users => {:type => 'User', :status => Principal::STATUS_ACTIVE}) }
36 has_many :enabled_modules, :dependent => :delete_all
36 has_many :enabled_modules, :dependent => :delete_all
37 has_and_belongs_to_many :trackers, lambda {order(:position)}
37 has_and_belongs_to_many :trackers, lambda {order(:position)}
38 has_many :issues, :dependent => :destroy
38 has_many :issues, :dependent => :destroy
39 has_many :issue_changes, :through => :issues, :source => :journals
39 has_many :issue_changes, :through => :issues, :source => :journals
40 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
40 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
41 belongs_to :default_version, :class_name => 'Version'
41 belongs_to :default_version, :class_name => 'Version'
42 has_many :time_entries, :dependent => :destroy
42 has_many :time_entries, :dependent => :destroy
43 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
43 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
44 has_many :documents, :dependent => :destroy
44 has_many :documents, :dependent => :destroy
45 has_many :news, lambda {includes(:author)}, :dependent => :destroy
45 has_many :news, lambda {includes(:author)}, :dependent => :destroy
46 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
46 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
47 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
47 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
48 has_one :repository, lambda {where(["is_default = ?", true])}
48 has_one :repository, lambda {where(["is_default = ?", true])}
49 has_many :repositories, :dependent => :destroy
49 has_many :repositories, :dependent => :destroy
50 has_many :changesets, :through => :repository
50 has_many :changesets, :through => :repository
51 has_one :wiki, :dependent => :destroy
51 has_one :wiki, :dependent => :destroy
52 # Custom field for the project issues
52 # Custom field for the project issues
53 has_and_belongs_to_many :issue_custom_fields,
53 has_and_belongs_to_many :issue_custom_fields,
54 lambda {order("#{CustomField.table_name}.position")},
54 lambda {order("#{CustomField.table_name}.position")},
55 :class_name => 'IssueCustomField',
55 :class_name => 'IssueCustomField',
56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
56 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
57 :association_foreign_key => 'custom_field_id'
57 :association_foreign_key => 'custom_field_id'
58
58
59 acts_as_attachable :view_permission => :view_files,
59 acts_as_attachable :view_permission => :view_files,
60 :edit_permission => :manage_files,
60 :edit_permission => :manage_files,
61 :delete_permission => :manage_files
61 :delete_permission => :manage_files
62
62
63 acts_as_customizable
63 acts_as_customizable
64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
64 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
65 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
65 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
66 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
67 :author => nil
67 :author => nil
68
68
69 attr_protected :status
69 attr_protected :status
70
70
71 validates_presence_of :name, :identifier
71 validates_presence_of :name, :identifier
72 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
72 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
73 validates_length_of :name, :maximum => 255
73 validates_length_of :name, :maximum => 255
74 validates_length_of :homepage, :maximum => 255
74 validates_length_of :homepage, :maximum => 255
75 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
75 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
76 # downcase letters, digits, dashes but not digits only
76 # downcase letters, digits, dashes but not digits only
77 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
77 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
78 # reserved words
78 # reserved words
79 validates_exclusion_of :identifier, :in => %w( new )
79 validates_exclusion_of :identifier, :in => %w( new )
80 validate :validate_parent
80 validate :validate_parent
81
81
82 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
82 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
83 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
83 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
84 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
84 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
85 before_destroy :delete_all_members
85 before_destroy :delete_all_members
86
86
87 scope :has_module, lambda {|mod|
87 scope :has_module, lambda {|mod|
88 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
88 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
89 }
89 }
90 scope :active, lambda { where(:status => STATUS_ACTIVE) }
90 scope :active, lambda { where(:status => STATUS_ACTIVE) }
91 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
91 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
92 scope :all_public, lambda { where(:is_public => true) }
92 scope :all_public, lambda { where(:is_public => true) }
93 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
93 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
94 scope :allowed_to, lambda {|*args|
94 scope :allowed_to, lambda {|*args|
95 user = User.current
95 user = User.current
96 permission = nil
96 permission = nil
97 if args.first.is_a?(Symbol)
97 if args.first.is_a?(Symbol)
98 permission = args.shift
98 permission = args.shift
99 else
99 else
100 user = args.shift
100 user = args.shift
101 permission = args.shift
101 permission = args.shift
102 end
102 end
103 where(Project.allowed_to_condition(user, permission, *args))
103 where(Project.allowed_to_condition(user, permission, *args))
104 }
104 }
105 scope :like, lambda {|arg|
105 scope :like, lambda {|arg|
106 if arg.blank?
106 if arg.blank?
107 where(nil)
107 where(nil)
108 else
108 else
109 pattern = "%#{arg.to_s.strip.downcase}%"
109 pattern = "%#{arg.to_s.strip.downcase}%"
110 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
110 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
111 end
111 end
112 }
112 }
113 scope :sorted, lambda {order(:lft)}
113 scope :sorted, lambda {order(:lft)}
114 scope :having_trackers, lambda {
114 scope :having_trackers, lambda {
115 where("#{Project.table_name}.id IN (SELECT DISTINCT project_id FROM #{table_name_prefix}projects_trackers#{table_name_suffix})")
115 where("#{Project.table_name}.id IN (SELECT DISTINCT project_id FROM #{table_name_prefix}projects_trackers#{table_name_suffix})")
116 }
116 }
117
117
118 def initialize(attributes=nil, *args)
118 def initialize(attributes=nil, *args)
119 super
119 super
120
120
121 initialized = (attributes || {}).stringify_keys
121 initialized = (attributes || {}).stringify_keys
122 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
122 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
123 self.identifier = Project.next_identifier
123 self.identifier = Project.next_identifier
124 end
124 end
125 if !initialized.key?('is_public')
125 if !initialized.key?('is_public')
126 self.is_public = Setting.default_projects_public?
126 self.is_public = Setting.default_projects_public?
127 end
127 end
128 if !initialized.key?('enabled_module_names')
128 if !initialized.key?('enabled_module_names')
129 self.enabled_module_names = Setting.default_projects_modules
129 self.enabled_module_names = Setting.default_projects_modules
130 end
130 end
131 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
131 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
132 default = Setting.default_projects_tracker_ids
132 default = Setting.default_projects_tracker_ids
133 if default.is_a?(Array)
133 if default.is_a?(Array)
134 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
135 else
135 else
136 self.trackers = Tracker.sorted.to_a
136 self.trackers = Tracker.sorted.to_a
137 end
137 end
138 end
138 end
139 end
139 end
140
140
141 def identifier=(identifier)
141 def identifier=(identifier)
142 super unless identifier_frozen?
142 super unless identifier_frozen?
143 end
143 end
144
144
145 def identifier_frozen?
145 def identifier_frozen?
146 errors[:identifier].blank? && !(new_record? || identifier.blank?)
146 errors[:identifier].blank? && !(new_record? || identifier.blank?)
147 end
147 end
148
148
149 # returns latest created projects
149 # returns latest created projects
150 # 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
151 def self.latest(user=nil, count=5)
151 def self.latest(user=nil, count=5)
152 visible(user).limit(count).
152 visible(user).limit(count).
153 order(:created_on => :desc).
153 order(:created_on => :desc).
154 where("#{table_name}.created_on >= ?", 30.days.ago).
154 where("#{table_name}.created_on >= ?", 30.days.ago).
155 to_a
155 to_a
156 end
156 end
157
157
158 # Returns true if the project is visible to +user+ or to the current user.
158 # Returns true if the project is visible to +user+ or to the current user.
159 def visible?(user=User.current)
159 def visible?(user=User.current)
160 user.allowed_to?(:view_project, self)
160 user.allowed_to?(:view_project, self)
161 end
161 end
162
162
163 # Returns a SQL conditions string used to find all projects visible by the specified user.
163 # Returns a SQL conditions string used to find all projects visible by the specified user.
164 #
164 #
165 # Examples:
165 # Examples:
166 # Project.visible_condition(admin) => "projects.status = 1"
166 # Project.visible_condition(admin) => "projects.status = 1"
167 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
167 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
168 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
168 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
169 def self.visible_condition(user, options={})
169 def self.visible_condition(user, options={})
170 allowed_to_condition(user, :view_project, options)
170 allowed_to_condition(user, :view_project, options)
171 end
171 end
172
172
173 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
173 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
174 #
174 #
175 # Valid options:
175 # Valid options:
176 # * :project => limit the condition to project
176 # * :project => limit the condition to project
177 # * :with_subprojects => limit the condition to project and its subprojects
177 # * :with_subprojects => limit the condition to project and its subprojects
178 # * :member => limit the condition to the user projects
178 # * :member => limit the condition to the user projects
179 def self.allowed_to_condition(user, permission, options={})
179 def self.allowed_to_condition(user, permission, options={})
180 perm = Redmine::AccessControl.permission(permission)
180 perm = Redmine::AccessControl.permission(permission)
181 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
181 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
182 if perm && perm.project_module
182 if perm && perm.project_module
183 # If the permission belongs to a project module, make sure the module is enabled
183 # If the permission belongs to a project module, make sure the module is enabled
184 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
184 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
185 end
185 end
186 if project = options[:project]
186 if project = options[:project]
187 project_statement = project.project_condition(options[:with_subprojects])
187 project_statement = project.project_condition(options[:with_subprojects])
188 base_statement = "(#{project_statement}) AND (#{base_statement})"
188 base_statement = "(#{project_statement}) AND (#{base_statement})"
189 end
189 end
190
190
191 if user.admin?
191 if user.admin?
192 base_statement
192 base_statement
193 else
193 else
194 statement_by_role = {}
194 statement_by_role = {}
195 unless options[:member]
195 unless options[:member]
196 role = user.builtin_role
196 role = user.builtin_role
197 if role.allowed_to?(permission)
197 if role.allowed_to?(permission)
198 s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
198 s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
199 if user.id
199 if user.id
200 s = "(#{s} AND #{Project.table_name}.id NOT IN (SELECT project_id FROM #{Member.table_name} WHERE user_id = #{user.id}))"
200 s = "(#{s} AND #{Project.table_name}.id NOT IN (SELECT project_id FROM #{Member.table_name} WHERE user_id = #{user.id}))"
201 end
201 end
202 statement_by_role[role] = s
202 statement_by_role[role] = s
203 end
203 end
204 end
204 end
205 user.projects_by_role.each do |role, projects|
205 user.projects_by_role.each do |role, projects|
206 if role.allowed_to?(permission) && projects.any?
206 if role.allowed_to?(permission) && projects.any?
207 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
207 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
208 end
208 end
209 end
209 end
210 if statement_by_role.empty?
210 if statement_by_role.empty?
211 "1=0"
211 "1=0"
212 else
212 else
213 if block_given?
213 if block_given?
214 statement_by_role.each do |role, statement|
214 statement_by_role.each do |role, statement|
215 if s = yield(role, user)
215 if s = yield(role, user)
216 statement_by_role[role] = "(#{statement} AND (#{s}))"
216 statement_by_role[role] = "(#{statement} AND (#{s}))"
217 end
217 end
218 end
218 end
219 end
219 end
220 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
220 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
221 end
221 end
222 end
222 end
223 end
223 end
224
224
225 def override_roles(role)
225 def override_roles(role)
226 @override_members ||= memberships.
226 @override_members ||= memberships.
227 joins(:principal).
227 joins(:principal).
228 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
228 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
229
229
230 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
230 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
231 member = @override_members.detect {|m| m.principal.is_a? group_class}
231 member = @override_members.detect {|m| m.principal.is_a? group_class}
232 member ? member.roles.to_a : [role]
232 member ? member.roles.to_a : [role]
233 end
233 end
234
234
235 def principals
235 def principals
236 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
236 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
237 end
237 end
238
238
239 def users
239 def users
240 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
240 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).distinct
241 end
241 end
242
242
243 # Returns the Systemwide and project specific activities
243 # Returns the Systemwide and project specific activities
244 def activities(include_inactive=false)
244 def activities(include_inactive=false)
245 t = TimeEntryActivity.table_name
245 t = TimeEntryActivity.table_name
246 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
246 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
247
247
248 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
248 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
249 if overridden_activity_ids.any?
249 if overridden_activity_ids.any?
250 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
250 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
251 end
251 end
252 unless include_inactive
252 unless include_inactive
253 scope = scope.active
253 scope = scope.active
254 end
254 end
255 scope
255 scope
256 end
256 end
257
257
258 # Will create a new Project specific Activity or update an existing one
258 # Will create a new Project specific Activity or update an existing one
259 #
259 #
260 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
260 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
261 # does not successfully save.
261 # does not successfully save.
262 def update_or_create_time_entry_activity(id, activity_hash)
262 def update_or_create_time_entry_activity(id, activity_hash)
263 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
263 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
264 self.create_time_entry_activity_if_needed(activity_hash)
264 self.create_time_entry_activity_if_needed(activity_hash)
265 else
265 else
266 activity = project.time_entry_activities.find_by_id(id.to_i)
266 activity = project.time_entry_activities.find_by_id(id.to_i)
267 activity.update_attributes(activity_hash) if activity
267 activity.update_attributes(activity_hash) if activity
268 end
268 end
269 end
269 end
270
270
271 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
271 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
272 #
272 #
273 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
273 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
274 # does not successfully save.
274 # does not successfully save.
275 def create_time_entry_activity_if_needed(activity)
275 def create_time_entry_activity_if_needed(activity)
276 if activity['parent_id']
276 if activity['parent_id']
277 parent_activity = TimeEntryActivity.find(activity['parent_id'])
277 parent_activity = TimeEntryActivity.find(activity['parent_id'])
278 activity['name'] = parent_activity.name
278 activity['name'] = parent_activity.name
279 activity['position'] = parent_activity.position
279 activity['position'] = parent_activity.position
280 if Enumeration.overriding_change?(activity, parent_activity)
280 if Enumeration.overriding_change?(activity, parent_activity)
281 project_activity = self.time_entry_activities.create(activity)
281 project_activity = self.time_entry_activities.create(activity)
282 if project_activity.new_record?
282 if project_activity.new_record?
283 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
283 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
284 else
284 else
285 self.time_entries.
285 self.time_entries.
286 where(:activity_id => parent_activity.id).
286 where(:activity_id => parent_activity.id).
287 update_all(:activity_id => project_activity.id)
287 update_all(:activity_id => project_activity.id)
288 end
288 end
289 end
289 end
290 end
290 end
291 end
291 end
292
292
293 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
293 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
294 #
294 #
295 # Examples:
295 # Examples:
296 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
296 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
297 # project.project_condition(false) => "projects.id = 1"
297 # project.project_condition(false) => "projects.id = 1"
298 def project_condition(with_subprojects)
298 def project_condition(with_subprojects)
299 cond = "#{Project.table_name}.id = #{id}"
299 cond = "#{Project.table_name}.id = #{id}"
300 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
300 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
301 cond
301 cond
302 end
302 end
303
303
304 def self.find(*args)
304 def self.find(*args)
305 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
305 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
306 project = find_by_identifier(*args)
306 project = find_by_identifier(*args)
307 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
307 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
308 project
308 project
309 else
309 else
310 super
310 super
311 end
311 end
312 end
312 end
313
313
314 def self.find_by_param(*args)
314 def self.find_by_param(*args)
315 self.find(*args)
315 self.find(*args)
316 end
316 end
317
317
318 alias :base_reload :reload
318 alias :base_reload :reload
319 def reload(*args)
319 def reload(*args)
320 @principals = nil
320 @principals = nil
321 @users = nil
321 @users = nil
322 @shared_versions = nil
322 @shared_versions = nil
323 @rolled_up_versions = nil
323 @rolled_up_versions = nil
324 @rolled_up_trackers = nil
324 @rolled_up_trackers = nil
325 @all_issue_custom_fields = nil
325 @all_issue_custom_fields = nil
326 @all_time_entry_custom_fields = nil
326 @all_time_entry_custom_fields = nil
327 @to_param = nil
327 @to_param = nil
328 @allowed_parents = nil
328 @allowed_parents = nil
329 @allowed_permissions = nil
329 @allowed_permissions = nil
330 @actions_allowed = nil
330 @actions_allowed = nil
331 @start_date = nil
331 @start_date = nil
332 @due_date = nil
332 @due_date = nil
333 @override_members = nil
333 @override_members = nil
334 @assignable_users = nil
334 @assignable_users = nil
335 base_reload(*args)
335 base_reload(*args)
336 end
336 end
337
337
338 def to_param
338 def to_param
339 if new_record?
339 if new_record?
340 nil
340 nil
341 else
341 else
342 # id is used for projects with a numeric identifier (compatibility)
342 # id is used for projects with a numeric identifier (compatibility)
343 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
343 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
344 end
344 end
345 end
345 end
346
346
347 def active?
347 def active?
348 self.status == STATUS_ACTIVE
348 self.status == STATUS_ACTIVE
349 end
349 end
350
350
351 def archived?
351 def archived?
352 self.status == STATUS_ARCHIVED
352 self.status == STATUS_ARCHIVED
353 end
353 end
354
354
355 # Archives the project and its descendants
355 # Archives the project and its descendants
356 def archive
356 def archive
357 # Check that there is no issue of a non descendant project that is assigned
357 # Check that there is no issue of a non descendant project that is assigned
358 # to one of the project or descendant versions
358 # to one of the project or descendant versions
359 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
359 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
360
360
361 if version_ids.any? &&
361 if version_ids.any? &&
362 Issue.
362 Issue.
363 includes(:project).
363 includes(:project).
364 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
364 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
365 where(:fixed_version_id => version_ids).
365 where(:fixed_version_id => version_ids).
366 exists?
366 exists?
367 return false
367 return false
368 end
368 end
369 Project.transaction do
369 Project.transaction do
370 archive!
370 archive!
371 end
371 end
372 true
372 true
373 end
373 end
374
374
375 # Unarchives the project
375 # Unarchives the project
376 # All its ancestors must be active
376 # All its ancestors must be active
377 def unarchive
377 def unarchive
378 return false if ancestors.detect {|a| !a.active?}
378 return false if ancestors.detect {|a| !a.active?}
379 update_attribute :status, STATUS_ACTIVE
379 update_attribute :status, STATUS_ACTIVE
380 end
380 end
381
381
382 def close
382 def close
383 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
383 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
384 end
384 end
385
385
386 def reopen
386 def reopen
387 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
387 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
388 end
388 end
389
389
390 # Returns an array of projects the project can be moved to
390 # Returns an array of projects the project can be moved to
391 # by the current user
391 # by the current user
392 def allowed_parents(user=User.current)
392 def allowed_parents(user=User.current)
393 return @allowed_parents if @allowed_parents
393 return @allowed_parents if @allowed_parents
394 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
394 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
395 @allowed_parents = @allowed_parents - self_and_descendants
395 @allowed_parents = @allowed_parents - self_and_descendants
396 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
396 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
397 @allowed_parents << nil
397 @allowed_parents << nil
398 end
398 end
399 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
399 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
400 @allowed_parents << parent
400 @allowed_parents << parent
401 end
401 end
402 @allowed_parents
402 @allowed_parents
403 end
403 end
404
404
405 # Sets the parent of the project with authorization check
405 # Sets the parent of the project with authorization check
406 def set_allowed_parent!(p)
406 def set_allowed_parent!(p)
407 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
407 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
408 p = p.id if p.is_a?(Project)
408 p = p.id if p.is_a?(Project)
409 send :safe_attributes, {:project_id => p}
409 send :safe_attributes, {:project_id => p}
410 save
410 save
411 end
411 end
412
412
413 # Sets the parent of the project and saves the project
413 # Sets the parent of the project and saves the project
414 # Argument can be either a Project, a String, a Fixnum or nil
414 # Argument can be either a Project, a String, a Fixnum or nil
415 def set_parent!(p)
415 def set_parent!(p)
416 if p.is_a?(Project)
416 if p.is_a?(Project)
417 self.parent = p
417 self.parent = p
418 else
418 else
419 self.parent_id = p
419 self.parent_id = p
420 end
420 end
421 save
421 save
422 end
422 end
423
423
424 # Returns a scope of the trackers used by the project and its active sub projects
424 # Returns a scope of the trackers used by the project and its active sub projects
425 def rolled_up_trackers(include_subprojects=true)
425 def rolled_up_trackers(include_subprojects=true)
426 if include_subprojects
426 if include_subprojects
427 @rolled_up_trackers ||= rolled_up_trackers_base_scope.
427 @rolled_up_trackers ||= rolled_up_trackers_base_scope.
428 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt)
428 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt)
429 else
429 else
430 rolled_up_trackers_base_scope.
430 rolled_up_trackers_base_scope.
431 where(:projects => {:id => id})
431 where(:projects => {:id => id})
432 end
432 end
433 end
433 end
434
434
435 def rolled_up_trackers_base_scope
435 def rolled_up_trackers_base_scope
436 Tracker.
436 Tracker.
437 joins(projects: :enabled_modules).
437 joins(projects: :enabled_modules).
438 where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED).
438 where("#{Project.table_name}.status <> ?", STATUS_ARCHIVED).
439 where(:enabled_modules => {:name => 'issue_tracking'}).
439 where(:enabled_modules => {:name => 'issue_tracking'}).
440 distinct.
440 distinct.
441 sorted
441 sorted
442 end
442 end
443
443
444 # Closes open and locked project versions that are completed
444 # Closes open and locked project versions that are completed
445 def close_completed_versions
445 def close_completed_versions
446 Version.transaction do
446 Version.transaction do
447 versions.where(:status => %w(open locked)).each do |version|
447 versions.where(:status => %w(open locked)).each do |version|
448 if version.completed?
448 if version.completed?
449 version.update_attribute(:status, 'closed')
449 version.update_attribute(:status, 'closed')
450 end
450 end
451 end
451 end
452 end
452 end
453 end
453 end
454
454
455 # Returns a scope of the Versions on subprojects
455 # Returns a scope of the Versions on subprojects
456 def rolled_up_versions
456 def rolled_up_versions
457 @rolled_up_versions ||=
457 @rolled_up_versions ||=
458 Version.
458 Version.
459 joins(:project).
459 joins(:project).
460 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
460 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
461 end
461 end
462
462
463 # Returns a scope of the Versions used by the project
463 # Returns a scope of the Versions used by the project
464 def shared_versions
464 def shared_versions
465 if new_record?
465 if new_record?
466 Version.
466 Version.
467 joins(:project).
467 joins(:project).
468 preload(:project).
468 preload(:project).
469 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
469 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
470 else
470 else
471 @shared_versions ||= begin
471 @shared_versions ||= begin
472 r = root? ? self : root
472 r = root? ? self : root
473 Version.
473 Version.
474 joins(:project).
474 joins(:project).
475 preload(:project).
475 preload(:project).
476 where("#{Project.table_name}.id = #{id}" +
476 where("#{Project.table_name}.id = #{id}" +
477 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
477 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
478 " #{Version.table_name}.sharing = 'system'" +
478 " #{Version.table_name}.sharing = 'system'" +
479 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
479 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
480 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
480 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
481 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
481 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
482 "))")
482 "))")
483 end
483 end
484 end
484 end
485 end
485 end
486
486
487 # Returns a hash of project users grouped by role
487 # Returns a hash of project users grouped by role
488 def users_by_role
488 def users_by_role
489 members.includes(:user, :roles).inject({}) do |h, m|
489 members.includes(:user, :roles).inject({}) do |h, m|
490 m.roles.each do |r|
490 m.roles.each do |r|
491 h[r] ||= []
491 h[r] ||= []
492 h[r] << m.user
492 h[r] << m.user
493 end
493 end
494 h
494 h
495 end
495 end
496 end
496 end
497
497
498 # Adds user as a project member with the default role
498 # Adds user as a project member with the default role
499 # Used for when a non-admin user creates a project
499 # Used for when a non-admin user creates a project
500 def add_default_member(user)
500 def add_default_member(user)
501 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
501 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
502 member = Member.new(:project => self, :principal => user, :roles => [role])
502 member = Member.new(:project => self, :principal => user, :roles => [role])
503 self.members << member
503 self.members << member
504 member
504 member
505 end
505 end
506
506
507 # Deletes all project's members
507 # Deletes all project's members
508 def delete_all_members
508 def delete_all_members
509 me, mr = Member.table_name, MemberRole.table_name
509 me, mr = Member.table_name, MemberRole.table_name
510 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
510 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
511 Member.delete_all(['project_id = ?', id])
511 Member.where(:project_id => id).delete_all
512 end
512 end
513
513
514 # Return a Principal scope of users/groups issues can be assigned to
514 # Return a Principal scope of users/groups issues can be assigned to
515 def assignable_users(tracker=nil)
515 def assignable_users(tracker=nil)
516 return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker]
516 return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker]
517
517
518 types = ['User']
518 types = ['User']
519 types << 'Group' if Setting.issue_group_assignment?
519 types << 'Group' if Setting.issue_group_assignment?
520
520
521 scope = Principal.
521 scope = Principal.
522 active.
522 active.
523 joins(:members => :roles).
523 joins(:members => :roles).
524 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
524 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
525 distinct.
525 distinct.
526 sorted
526 sorted
527
527
528 if tracker
528 if tracker
529 # Rejects users that cannot the view the tracker
529 # Rejects users that cannot the view the tracker
530 roles = Role.where(:assignable => true).select {|role| role.permissions_tracker?(:view_issues, tracker)}
530 roles = Role.where(:assignable => true).select {|role| role.permissions_tracker?(:view_issues, tracker)}
531 scope = scope.where(:roles => {:id => roles.map(&:id)})
531 scope = scope.where(:roles => {:id => roles.map(&:id)})
532 end
532 end
533
533
534 @assignable_users ||= {}
534 @assignable_users ||= {}
535 @assignable_users[tracker] = scope
535 @assignable_users[tracker] = scope
536 end
536 end
537
537
538 # Returns the mail addresses of users that should be always notified on project events
538 # Returns the mail addresses of users that should be always notified on project events
539 def recipients
539 def recipients
540 notified_users.collect {|user| user.mail}
540 notified_users.collect {|user| user.mail}
541 end
541 end
542
542
543 # Returns the users that should be notified on project events
543 # Returns the users that should be notified on project events
544 def notified_users
544 def notified_users
545 # TODO: User part should be extracted to User#notify_about?
545 # TODO: User part should be extracted to User#notify_about?
546 members.preload(:principal).select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
546 members.preload(:principal).select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
547 end
547 end
548
548
549 # Returns a scope of all custom fields enabled for project issues
549 # Returns a scope of all custom fields enabled for project issues
550 # (explicitly associated custom fields and custom fields enabled for all projects)
550 # (explicitly associated custom fields and custom fields enabled for all projects)
551 def all_issue_custom_fields
551 def all_issue_custom_fields
552 if new_record?
552 if new_record?
553 @all_issue_custom_fields ||= IssueCustomField.
553 @all_issue_custom_fields ||= IssueCustomField.
554 sorted.
554 sorted.
555 where("is_for_all = ? OR id IN (?)", true, issue_custom_field_ids)
555 where("is_for_all = ? OR id IN (?)", true, issue_custom_field_ids)
556 else
556 else
557 @all_issue_custom_fields ||= IssueCustomField.
557 @all_issue_custom_fields ||= IssueCustomField.
558 sorted.
558 sorted.
559 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
559 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
560 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
560 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
561 " WHERE cfp.project_id = ?)", true, id)
561 " WHERE cfp.project_id = ?)", true, id)
562 end
562 end
563 end
563 end
564
564
565 def project
565 def project
566 self
566 self
567 end
567 end
568
568
569 def <=>(project)
569 def <=>(project)
570 name.casecmp(project.name)
570 name.casecmp(project.name)
571 end
571 end
572
572
573 def to_s
573 def to_s
574 name
574 name
575 end
575 end
576
576
577 # Returns a short description of the projects (first lines)
577 # Returns a short description of the projects (first lines)
578 def short_description(length = 255)
578 def short_description(length = 255)
579 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
579 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
580 end
580 end
581
581
582 def css_classes
582 def css_classes
583 s = 'project'
583 s = 'project'
584 s << ' root' if root?
584 s << ' root' if root?
585 s << ' child' if child?
585 s << ' child' if child?
586 s << (leaf? ? ' leaf' : ' parent')
586 s << (leaf? ? ' leaf' : ' parent')
587 unless active?
587 unless active?
588 if archived?
588 if archived?
589 s << ' archived'
589 s << ' archived'
590 else
590 else
591 s << ' closed'
591 s << ' closed'
592 end
592 end
593 end
593 end
594 s
594 s
595 end
595 end
596
596
597 # The earliest start date of a project, based on it's issues and versions
597 # The earliest start date of a project, based on it's issues and versions
598 def start_date
598 def start_date
599 @start_date ||= [
599 @start_date ||= [
600 issues.minimum('start_date'),
600 issues.minimum('start_date'),
601 shared_versions.minimum('effective_date'),
601 shared_versions.minimum('effective_date'),
602 Issue.fixed_version(shared_versions).minimum('start_date')
602 Issue.fixed_version(shared_versions).minimum('start_date')
603 ].compact.min
603 ].compact.min
604 end
604 end
605
605
606 # The latest due date of an issue or version
606 # The latest due date of an issue or version
607 def due_date
607 def due_date
608 @due_date ||= [
608 @due_date ||= [
609 issues.maximum('due_date'),
609 issues.maximum('due_date'),
610 shared_versions.maximum('effective_date'),
610 shared_versions.maximum('effective_date'),
611 Issue.fixed_version(shared_versions).maximum('due_date')
611 Issue.fixed_version(shared_versions).maximum('due_date')
612 ].compact.max
612 ].compact.max
613 end
613 end
614
614
615 def overdue?
615 def overdue?
616 active? && !due_date.nil? && (due_date < User.current.today)
616 active? && !due_date.nil? && (due_date < User.current.today)
617 end
617 end
618
618
619 # Returns the percent completed for this project, based on the
619 # Returns the percent completed for this project, based on the
620 # progress on it's versions.
620 # progress on it's versions.
621 def completed_percent(options={:include_subprojects => false})
621 def completed_percent(options={:include_subprojects => false})
622 if options.delete(:include_subprojects)
622 if options.delete(:include_subprojects)
623 total = self_and_descendants.collect(&:completed_percent).sum
623 total = self_and_descendants.collect(&:completed_percent).sum
624
624
625 total / self_and_descendants.count
625 total / self_and_descendants.count
626 else
626 else
627 if versions.count > 0
627 if versions.count > 0
628 total = versions.collect(&:completed_percent).sum
628 total = versions.collect(&:completed_percent).sum
629
629
630 total / versions.count
630 total / versions.count
631 else
631 else
632 100
632 100
633 end
633 end
634 end
634 end
635 end
635 end
636
636
637 # Return true if this project allows to do the specified action.
637 # Return true if this project allows to do the specified action.
638 # action can be:
638 # action can be:
639 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
639 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
640 # * a permission Symbol (eg. :edit_project)
640 # * a permission Symbol (eg. :edit_project)
641 def allows_to?(action)
641 def allows_to?(action)
642 if archived?
642 if archived?
643 # No action allowed on archived projects
643 # No action allowed on archived projects
644 return false
644 return false
645 end
645 end
646 unless active? || Redmine::AccessControl.read_action?(action)
646 unless active? || Redmine::AccessControl.read_action?(action)
647 # No write action allowed on closed projects
647 # No write action allowed on closed projects
648 return false
648 return false
649 end
649 end
650 # No action allowed on disabled modules
650 # No action allowed on disabled modules
651 if action.is_a? Hash
651 if action.is_a? Hash
652 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
652 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
653 else
653 else
654 allowed_permissions.include? action
654 allowed_permissions.include? action
655 end
655 end
656 end
656 end
657
657
658 # Return the enabled module with the given name
658 # Return the enabled module with the given name
659 # or nil if the module is not enabled for the project
659 # or nil if the module is not enabled for the project
660 def enabled_module(name)
660 def enabled_module(name)
661 name = name.to_s
661 name = name.to_s
662 enabled_modules.detect {|m| m.name == name}
662 enabled_modules.detect {|m| m.name == name}
663 end
663 end
664
664
665 # Return true if the module with the given name is enabled
665 # Return true if the module with the given name is enabled
666 def module_enabled?(name)
666 def module_enabled?(name)
667 enabled_module(name).present?
667 enabled_module(name).present?
668 end
668 end
669
669
670 def enabled_module_names=(module_names)
670 def enabled_module_names=(module_names)
671 if module_names && module_names.is_a?(Array)
671 if module_names && module_names.is_a?(Array)
672 module_names = module_names.collect(&:to_s).reject(&:blank?)
672 module_names = module_names.collect(&:to_s).reject(&:blank?)
673 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
673 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
674 else
674 else
675 enabled_modules.clear
675 enabled_modules.clear
676 end
676 end
677 end
677 end
678
678
679 # Returns an array of the enabled modules names
679 # Returns an array of the enabled modules names
680 def enabled_module_names
680 def enabled_module_names
681 enabled_modules.collect(&:name)
681 enabled_modules.collect(&:name)
682 end
682 end
683
683
684 # Enable a specific module
684 # Enable a specific module
685 #
685 #
686 # Examples:
686 # Examples:
687 # project.enable_module!(:issue_tracking)
687 # project.enable_module!(:issue_tracking)
688 # project.enable_module!("issue_tracking")
688 # project.enable_module!("issue_tracking")
689 def enable_module!(name)
689 def enable_module!(name)
690 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
690 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
691 end
691 end
692
692
693 # Disable a module if it exists
693 # Disable a module if it exists
694 #
694 #
695 # Examples:
695 # Examples:
696 # project.disable_module!(:issue_tracking)
696 # project.disable_module!(:issue_tracking)
697 # project.disable_module!("issue_tracking")
697 # project.disable_module!("issue_tracking")
698 # project.disable_module!(project.enabled_modules.first)
698 # project.disable_module!(project.enabled_modules.first)
699 def disable_module!(target)
699 def disable_module!(target)
700 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
700 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
701 target.destroy unless target.blank?
701 target.destroy unless target.blank?
702 end
702 end
703
703
704 safe_attributes 'name',
704 safe_attributes 'name',
705 'description',
705 'description',
706 'homepage',
706 'homepage',
707 'is_public',
707 'is_public',
708 'identifier',
708 'identifier',
709 'custom_field_values',
709 'custom_field_values',
710 'custom_fields',
710 'custom_fields',
711 'tracker_ids',
711 'tracker_ids',
712 'issue_custom_field_ids',
712 'issue_custom_field_ids',
713 'parent_id',
713 'parent_id',
714 'default_version_id'
714 'default_version_id'
715
715
716 safe_attributes 'enabled_module_names',
716 safe_attributes 'enabled_module_names',
717 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
717 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
718
718
719 safe_attributes 'inherit_members',
719 safe_attributes 'inherit_members',
720 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
720 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
721
721
722 def safe_attributes=(attrs, user=User.current)
722 def safe_attributes=(attrs, user=User.current)
723 return unless attrs.is_a?(Hash)
723 return unless attrs.is_a?(Hash)
724 attrs = attrs.deep_dup
724 attrs = attrs.deep_dup
725
725
726 @unallowed_parent_id = nil
726 @unallowed_parent_id = nil
727 if new_record? || attrs.key?('parent_id')
727 if new_record? || attrs.key?('parent_id')
728 parent_id_param = attrs['parent_id'].to_s
728 parent_id_param = attrs['parent_id'].to_s
729 if new_record? || parent_id_param != parent_id.to_s
729 if new_record? || parent_id_param != parent_id.to_s
730 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
730 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
731 unless allowed_parents(user).include?(p)
731 unless allowed_parents(user).include?(p)
732 attrs.delete('parent_id')
732 attrs.delete('parent_id')
733 @unallowed_parent_id = true
733 @unallowed_parent_id = true
734 end
734 end
735 end
735 end
736 end
736 end
737
737
738 super(attrs, user)
738 super(attrs, user)
739 end
739 end
740
740
741 # Returns an auto-generated project identifier based on the last identifier used
741 # Returns an auto-generated project identifier based on the last identifier used
742 def self.next_identifier
742 def self.next_identifier
743 p = Project.order('id DESC').first
743 p = Project.order('id DESC').first
744 p.nil? ? nil : p.identifier.to_s.succ
744 p.nil? ? nil : p.identifier.to_s.succ
745 end
745 end
746
746
747 # Copies and saves the Project instance based on the +project+.
747 # Copies and saves the Project instance based on the +project+.
748 # Duplicates the source project's:
748 # Duplicates the source project's:
749 # * Wiki
749 # * Wiki
750 # * Versions
750 # * Versions
751 # * Categories
751 # * Categories
752 # * Issues
752 # * Issues
753 # * Members
753 # * Members
754 # * Queries
754 # * Queries
755 #
755 #
756 # Accepts an +options+ argument to specify what to copy
756 # Accepts an +options+ argument to specify what to copy
757 #
757 #
758 # Examples:
758 # Examples:
759 # project.copy(1) # => copies everything
759 # project.copy(1) # => copies everything
760 # project.copy(1, :only => 'members') # => copies members only
760 # project.copy(1, :only => 'members') # => copies members only
761 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
761 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
762 def copy(project, options={})
762 def copy(project, options={})
763 project = project.is_a?(Project) ? project : Project.find(project)
763 project = project.is_a?(Project) ? project : Project.find(project)
764
764
765 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
765 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
766 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
766 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
767
767
768 Project.transaction do
768 Project.transaction do
769 if save
769 if save
770 reload
770 reload
771 to_be_copied.each do |name|
771 to_be_copied.each do |name|
772 send "copy_#{name}", project
772 send "copy_#{name}", project
773 end
773 end
774 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
774 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
775 save
775 save
776 else
776 else
777 false
777 false
778 end
778 end
779 end
779 end
780 end
780 end
781
781
782 def member_principals
782 def member_principals
783 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
783 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
784 memberships.active
784 memberships.active
785 end
785 end
786
786
787 # Returns a new unsaved Project instance with attributes copied from +project+
787 # Returns a new unsaved Project instance with attributes copied from +project+
788 def self.copy_from(project)
788 def self.copy_from(project)
789 project = project.is_a?(Project) ? project : Project.find(project)
789 project = project.is_a?(Project) ? project : Project.find(project)
790 # clear unique attributes
790 # clear unique attributes
791 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
791 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
792 copy = Project.new(attributes)
792 copy = Project.new(attributes)
793 copy.enabled_module_names = project.enabled_module_names
793 copy.enabled_module_names = project.enabled_module_names
794 copy.trackers = project.trackers
794 copy.trackers = project.trackers
795 copy.custom_values = project.custom_values.collect {|v| v.clone}
795 copy.custom_values = project.custom_values.collect {|v| v.clone}
796 copy.issue_custom_fields = project.issue_custom_fields
796 copy.issue_custom_fields = project.issue_custom_fields
797 copy
797 copy
798 end
798 end
799
799
800 # Yields the given block for each project with its level in the tree
800 # Yields the given block for each project with its level in the tree
801 def self.project_tree(projects, &block)
801 def self.project_tree(projects, &block)
802 ancestors = []
802 ancestors = []
803 projects.sort_by(&:lft).each do |project|
803 projects.sort_by(&:lft).each do |project|
804 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
804 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
805 ancestors.pop
805 ancestors.pop
806 end
806 end
807 yield project, ancestors.size
807 yield project, ancestors.size
808 ancestors << project
808 ancestors << project
809 end
809 end
810 end
810 end
811
811
812 private
812 private
813
813
814 def update_inherited_members
814 def update_inherited_members
815 if parent
815 if parent
816 if inherit_members? && !inherit_members_was
816 if inherit_members? && !inherit_members_was
817 remove_inherited_member_roles
817 remove_inherited_member_roles
818 add_inherited_member_roles
818 add_inherited_member_roles
819 elsif !inherit_members? && inherit_members_was
819 elsif !inherit_members? && inherit_members_was
820 remove_inherited_member_roles
820 remove_inherited_member_roles
821 end
821 end
822 end
822 end
823 end
823 end
824
824
825 def remove_inherited_member_roles
825 def remove_inherited_member_roles
826 member_roles = memberships.map(&:member_roles).flatten
826 member_roles = memberships.map(&:member_roles).flatten
827 member_role_ids = member_roles.map(&:id)
827 member_role_ids = member_roles.map(&:id)
828 member_roles.each do |member_role|
828 member_roles.each do |member_role|
829 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
829 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
830 member_role.destroy
830 member_role.destroy
831 end
831 end
832 end
832 end
833 end
833 end
834
834
835 def add_inherited_member_roles
835 def add_inherited_member_roles
836 if inherit_members? && parent
836 if inherit_members? && parent
837 parent.memberships.each do |parent_member|
837 parent.memberships.each do |parent_member|
838 member = Member.find_or_new(self.id, parent_member.user_id)
838 member = Member.find_or_new(self.id, parent_member.user_id)
839 parent_member.member_roles.each do |parent_member_role|
839 parent_member.member_roles.each do |parent_member_role|
840 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
840 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
841 end
841 end
842 member.save!
842 member.save!
843 end
843 end
844 memberships.reset
844 memberships.reset
845 end
845 end
846 end
846 end
847
847
848 def update_versions_from_hierarchy_change
848 def update_versions_from_hierarchy_change
849 Issue.update_versions_from_hierarchy_change(self)
849 Issue.update_versions_from_hierarchy_change(self)
850 end
850 end
851
851
852 def validate_parent
852 def validate_parent
853 if @unallowed_parent_id
853 if @unallowed_parent_id
854 errors.add(:parent_id, :invalid)
854 errors.add(:parent_id, :invalid)
855 elsif parent_id_changed?
855 elsif parent_id_changed?
856 unless parent.nil? || (parent.active? && move_possible?(parent))
856 unless parent.nil? || (parent.active? && move_possible?(parent))
857 errors.add(:parent_id, :invalid)
857 errors.add(:parent_id, :invalid)
858 end
858 end
859 end
859 end
860 end
860 end
861
861
862 # Copies wiki from +project+
862 # Copies wiki from +project+
863 def copy_wiki(project)
863 def copy_wiki(project)
864 # Check that the source project has a wiki first
864 # Check that the source project has a wiki first
865 unless project.wiki.nil?
865 unless project.wiki.nil?
866 wiki = self.wiki || Wiki.new
866 wiki = self.wiki || Wiki.new
867 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
867 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
868 wiki_pages_map = {}
868 wiki_pages_map = {}
869 project.wiki.pages.each do |page|
869 project.wiki.pages.each do |page|
870 # Skip pages without content
870 # Skip pages without content
871 next if page.content.nil?
871 next if page.content.nil?
872 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
872 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
873 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
873 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
874 new_wiki_page.content = new_wiki_content
874 new_wiki_page.content = new_wiki_content
875 wiki.pages << new_wiki_page
875 wiki.pages << new_wiki_page
876 wiki_pages_map[page.id] = new_wiki_page
876 wiki_pages_map[page.id] = new_wiki_page
877 end
877 end
878
878
879 self.wiki = wiki
879 self.wiki = wiki
880 wiki.save
880 wiki.save
881 # Reproduce page hierarchy
881 # Reproduce page hierarchy
882 project.wiki.pages.each do |page|
882 project.wiki.pages.each do |page|
883 if page.parent_id && wiki_pages_map[page.id]
883 if page.parent_id && wiki_pages_map[page.id]
884 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
884 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
885 wiki_pages_map[page.id].save
885 wiki_pages_map[page.id].save
886 end
886 end
887 end
887 end
888 end
888 end
889 end
889 end
890
890
891 # Copies versions from +project+
891 # Copies versions from +project+
892 def copy_versions(project)
892 def copy_versions(project)
893 project.versions.each do |version|
893 project.versions.each do |version|
894 new_version = Version.new
894 new_version = Version.new
895 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
895 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
896 self.versions << new_version
896 self.versions << new_version
897 end
897 end
898 end
898 end
899
899
900 # Copies issue categories from +project+
900 # Copies issue categories from +project+
901 def copy_issue_categories(project)
901 def copy_issue_categories(project)
902 project.issue_categories.each do |issue_category|
902 project.issue_categories.each do |issue_category|
903 new_issue_category = IssueCategory.new
903 new_issue_category = IssueCategory.new
904 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
904 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
905 self.issue_categories << new_issue_category
905 self.issue_categories << new_issue_category
906 end
906 end
907 end
907 end
908
908
909 # Copies issues from +project+
909 # Copies issues from +project+
910 def copy_issues(project)
910 def copy_issues(project)
911 # Stores the source issue id as a key and the copied issues as the
911 # Stores the source issue id as a key and the copied issues as the
912 # value. Used to map the two together for issue relations.
912 # value. Used to map the two together for issue relations.
913 issues_map = {}
913 issues_map = {}
914
914
915 # Store status and reopen locked/closed versions
915 # Store status and reopen locked/closed versions
916 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
916 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
917 version_statuses.each do |version, status|
917 version_statuses.each do |version, status|
918 version.update_attribute :status, 'open'
918 version.update_attribute :status, 'open'
919 end
919 end
920
920
921 # Get issues sorted by root_id, lft so that parent issues
921 # Get issues sorted by root_id, lft so that parent issues
922 # get copied before their children
922 # get copied before their children
923 project.issues.reorder('root_id, lft').each do |issue|
923 project.issues.reorder('root_id, lft').each do |issue|
924 new_issue = Issue.new
924 new_issue = Issue.new
925 new_issue.copy_from(issue, :subtasks => false, :link => false)
925 new_issue.copy_from(issue, :subtasks => false, :link => false)
926 new_issue.project = self
926 new_issue.project = self
927 # Changing project resets the custom field values
927 # Changing project resets the custom field values
928 # TODO: handle this in Issue#project=
928 # TODO: handle this in Issue#project=
929 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
929 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
930 # Reassign fixed_versions by name, since names are unique per project
930 # Reassign fixed_versions by name, since names are unique per project
931 if issue.fixed_version && issue.fixed_version.project == project
931 if issue.fixed_version && issue.fixed_version.project == project
932 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
932 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
933 end
933 end
934 # Reassign version custom field values
934 # Reassign version custom field values
935 new_issue.custom_field_values.each do |custom_value|
935 new_issue.custom_field_values.each do |custom_value|
936 if custom_value.custom_field.field_format == 'version' && custom_value.value.present?
936 if custom_value.custom_field.field_format == 'version' && custom_value.value.present?
937 versions = Version.where(:id => custom_value.value).to_a
937 versions = Version.where(:id => custom_value.value).to_a
938 new_value = versions.map do |version|
938 new_value = versions.map do |version|
939 if version.project == project
939 if version.project == project
940 self.versions.detect {|v| v.name == version.name}.try(:id)
940 self.versions.detect {|v| v.name == version.name}.try(:id)
941 else
941 else
942 version.id
942 version.id
943 end
943 end
944 end
944 end
945 new_value.compact!
945 new_value.compact!
946 new_value = new_value.first unless custom_value.custom_field.multiple?
946 new_value = new_value.first unless custom_value.custom_field.multiple?
947 custom_value.value = new_value
947 custom_value.value = new_value
948 end
948 end
949 end
949 end
950 # Reassign the category by name, since names are unique per project
950 # Reassign the category by name, since names are unique per project
951 if issue.category
951 if issue.category
952 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
952 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
953 end
953 end
954 # Parent issue
954 # Parent issue
955 if issue.parent_id
955 if issue.parent_id
956 if copied_parent = issues_map[issue.parent_id]
956 if copied_parent = issues_map[issue.parent_id]
957 new_issue.parent_issue_id = copied_parent.id
957 new_issue.parent_issue_id = copied_parent.id
958 end
958 end
959 end
959 end
960
960
961 self.issues << new_issue
961 self.issues << new_issue
962 if new_issue.new_record?
962 if new_issue.new_record?
963 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
963 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
964 else
964 else
965 issues_map[issue.id] = new_issue unless new_issue.new_record?
965 issues_map[issue.id] = new_issue unless new_issue.new_record?
966 end
966 end
967 end
967 end
968
968
969 # Restore locked/closed version statuses
969 # Restore locked/closed version statuses
970 version_statuses.each do |version, status|
970 version_statuses.each do |version, status|
971 version.update_attribute :status, status
971 version.update_attribute :status, status
972 end
972 end
973
973
974 # Relations after in case issues related each other
974 # Relations after in case issues related each other
975 project.issues.each do |issue|
975 project.issues.each do |issue|
976 new_issue = issues_map[issue.id]
976 new_issue = issues_map[issue.id]
977 unless new_issue
977 unless new_issue
978 # Issue was not copied
978 # Issue was not copied
979 next
979 next
980 end
980 end
981
981
982 # Relations
982 # Relations
983 issue.relations_from.each do |source_relation|
983 issue.relations_from.each do |source_relation|
984 new_issue_relation = IssueRelation.new
984 new_issue_relation = IssueRelation.new
985 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
985 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
986 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
986 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
987 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
987 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
988 new_issue_relation.issue_to = source_relation.issue_to
988 new_issue_relation.issue_to = source_relation.issue_to
989 end
989 end
990 new_issue.relations_from << new_issue_relation
990 new_issue.relations_from << new_issue_relation
991 end
991 end
992
992
993 issue.relations_to.each do |source_relation|
993 issue.relations_to.each do |source_relation|
994 new_issue_relation = IssueRelation.new
994 new_issue_relation = IssueRelation.new
995 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
995 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
996 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
996 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
997 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
997 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
998 new_issue_relation.issue_from = source_relation.issue_from
998 new_issue_relation.issue_from = source_relation.issue_from
999 end
999 end
1000 new_issue.relations_to << new_issue_relation
1000 new_issue.relations_to << new_issue_relation
1001 end
1001 end
1002 end
1002 end
1003 end
1003 end
1004
1004
1005 # Copies members from +project+
1005 # Copies members from +project+
1006 def copy_members(project)
1006 def copy_members(project)
1007 # Copy users first, then groups to handle members with inherited and given roles
1007 # Copy users first, then groups to handle members with inherited and given roles
1008 members_to_copy = []
1008 members_to_copy = []
1009 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
1009 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
1010 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
1010 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
1011
1011
1012 members_to_copy.each do |member|
1012 members_to_copy.each do |member|
1013 new_member = Member.new
1013 new_member = Member.new
1014 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
1014 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
1015 # only copy non inherited roles
1015 # only copy non inherited roles
1016 # inherited roles will be added when copying the group membership
1016 # inherited roles will be added when copying the group membership
1017 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
1017 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
1018 next if role_ids.empty?
1018 next if role_ids.empty?
1019 new_member.role_ids = role_ids
1019 new_member.role_ids = role_ids
1020 new_member.project = self
1020 new_member.project = self
1021 self.members << new_member
1021 self.members << new_member
1022 end
1022 end
1023 end
1023 end
1024
1024
1025 # Copies queries from +project+
1025 # Copies queries from +project+
1026 def copy_queries(project)
1026 def copy_queries(project)
1027 project.queries.each do |query|
1027 project.queries.each do |query|
1028 new_query = IssueQuery.new
1028 new_query = IssueQuery.new
1029 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
1029 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
1030 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
1030 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
1031 new_query.project = self
1031 new_query.project = self
1032 new_query.user_id = query.user_id
1032 new_query.user_id = query.user_id
1033 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
1033 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
1034 self.queries << new_query
1034 self.queries << new_query
1035 end
1035 end
1036 end
1036 end
1037
1037
1038 # Copies boards from +project+
1038 # Copies boards from +project+
1039 def copy_boards(project)
1039 def copy_boards(project)
1040 project.boards.each do |board|
1040 project.boards.each do |board|
1041 new_board = Board.new
1041 new_board = Board.new
1042 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
1042 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
1043 new_board.project = self
1043 new_board.project = self
1044 self.boards << new_board
1044 self.boards << new_board
1045 end
1045 end
1046 end
1046 end
1047
1047
1048 def allowed_permissions
1048 def allowed_permissions
1049 @allowed_permissions ||= begin
1049 @allowed_permissions ||= begin
1050 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
1050 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
1051 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
1051 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
1052 end
1052 end
1053 end
1053 end
1054
1054
1055 def allowed_actions
1055 def allowed_actions
1056 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
1056 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
1057 end
1057 end
1058
1058
1059 # Archives subprojects recursively
1059 # Archives subprojects recursively
1060 def archive!
1060 def archive!
1061 children.each do |subproject|
1061 children.each do |subproject|
1062 subproject.send :archive!
1062 subproject.send :archive!
1063 end
1063 end
1064 update_attribute :status, STATUS_ARCHIVED
1064 update_attribute :status, STATUS_ARCHIVED
1065 end
1065 end
1066 end
1066 end
@@ -1,15 +1,15
1 class SetTopicAuthorsAsWatchers < ActiveRecord::Migration
1 class SetTopicAuthorsAsWatchers < ActiveRecord::Migration
2 def self.up
2 def self.up
3 # Sets active users who created/replied a topic as watchers of the topic
3 # Sets active users who created/replied a topic as watchers of the topic
4 # so that the new watch functionality at topic level doesn't affect notifications behaviour
4 # so that the new watch functionality at topic level doesn't affect notifications behaviour
5 Message.connection.execute("INSERT INTO #{Watcher.table_name} (watchable_type, watchable_id, user_id)" +
5 Message.connection.execute("INSERT INTO #{Watcher.table_name} (watchable_type, watchable_id, user_id)" +
6 " SELECT DISTINCT 'Message', COALESCE(m.parent_id, m.id), m.author_id" +
6 " SELECT DISTINCT 'Message', COALESCE(m.parent_id, m.id), m.author_id" +
7 " FROM #{Message.table_name} m, #{User.table_name} u" +
7 " FROM #{Message.table_name} m, #{User.table_name} u" +
8 " WHERE m.author_id = u.id AND u.status = 1")
8 " WHERE m.author_id = u.id AND u.status = 1")
9 end
9 end
10
10
11 def self.down
11 def self.down
12 # Removes all message watchers
12 # Removes all message watchers
13 Watcher.delete_all("watchable_type = 'Message'")
13 Watcher.where("watchable_type = 'Message'").delete_all
14 end
14 end
15 end
15 end
@@ -1,12 +1,12
1 class EnableCalendarAndGanttModulesWhereAppropriate < ActiveRecord::Migration
1 class EnableCalendarAndGanttModulesWhereAppropriate < ActiveRecord::Migration
2 def self.up
2 def self.up
3 EnabledModule.where(:name => 'issue_tracking').each do |e|
3 EnabledModule.where(:name => 'issue_tracking').each do |e|
4 EnabledModule.create(:name => 'calendar', :project_id => e.project_id)
4 EnabledModule.create(:name => 'calendar', :project_id => e.project_id)
5 EnabledModule.create(:name => 'gantt', :project_id => e.project_id)
5 EnabledModule.create(:name => 'gantt', :project_id => e.project_id)
6 end
6 end
7 end
7 end
8
8
9 def self.down
9 def self.down
10 EnabledModule.delete_all("name = 'calendar' OR name = 'gantt'")
10 EnabledModule.where("name = 'calendar' OR name = 'gantt'").delete_all
11 end
11 end
12 end
12 end
@@ -1,22 +1,22
1 class AddUniqueIndexOnMembers < ActiveRecord::Migration
1 class AddUniqueIndexOnMembers < ActiveRecord::Migration
2 def self.up
2 def self.up
3 # Clean and reassign MemberRole rows if needed
3 # Clean and reassign MemberRole rows if needed
4 MemberRole.delete_all("member_id NOT IN (SELECT id FROM #{Member.table_name})")
4 MemberRole.where("member_id NOT IN (SELECT id FROM #{Member.table_name})").delete_all
5 MemberRole.update_all("member_id =" +
5 MemberRole.update_all("member_id =" +
6 " (SELECT min(m2.id) FROM #{Member.table_name} m1, #{Member.table_name} m2" +
6 " (SELECT min(m2.id) FROM #{Member.table_name} m1, #{Member.table_name} m2" +
7 " WHERE m1.user_id = m2.user_id AND m1.project_id = m2.project_id" +
7 " WHERE m1.user_id = m2.user_id AND m1.project_id = m2.project_id" +
8 " AND m1.id = #{MemberRole.table_name}.member_id)")
8 " AND m1.id = #{MemberRole.table_name}.member_id)")
9 # Remove duplicates
9 # Remove duplicates
10 Member.connection.select_values("SELECT m.id FROM #{Member.table_name} m" +
10 Member.connection.select_values("SELECT m.id FROM #{Member.table_name} m" +
11 " WHERE m.id > (SELECT min(m1.id) FROM #{Member.table_name} m1 WHERE m1.user_id = m.user_id AND m1.project_id = m.project_id)").each do |i|
11 " WHERE m.id > (SELECT min(m1.id) FROM #{Member.table_name} m1 WHERE m1.user_id = m.user_id AND m1.project_id = m.project_id)").each do |i|
12 Member.delete_all(["id = ?", i])
12 Member.where(["id = ?", i]).delete_all
13 end
13 end
14
14
15 # Then add a unique index
15 # Then add a unique index
16 add_index :members, [:user_id, :project_id], :unique => true
16 add_index :members, [:user_id, :project_id], :unique => true
17 end
17 end
18
18
19 def self.down
19 def self.down
20 remove_index :members, [:user_id, :project_id]
20 remove_index :members, [:user_id, :project_id]
21 end
21 end
22 end
22 end
@@ -1,16 +1,16
1 class AddUniqueIndexToIssueRelations < ActiveRecord::Migration
1 class AddUniqueIndexToIssueRelations < ActiveRecord::Migration
2 def self.up
2 def self.up
3
3
4 # Remove duplicates
4 # Remove duplicates
5 IssueRelation.connection.select_values("SELECT r.id FROM #{IssueRelation.table_name} r" +
5 IssueRelation.connection.select_values("SELECT r.id FROM #{IssueRelation.table_name} r" +
6 " WHERE r.id > (SELECT min(r1.id) FROM #{IssueRelation.table_name} r1 WHERE r1.issue_from_id = r.issue_from_id AND r1.issue_to_id = r.issue_to_id)").each do |i|
6 " WHERE r.id > (SELECT min(r1.id) FROM #{IssueRelation.table_name} r1 WHERE r1.issue_from_id = r.issue_from_id AND r1.issue_to_id = r.issue_to_id)").each do |i|
7 IssueRelation.delete_all(["id = ?", i])
7 IssueRelation.where(["id = ?", i]).delete_all
8 end
8 end
9
9
10 add_index :issue_relations, [:issue_from_id, :issue_to_id], :unique => true
10 add_index :issue_relations, [:issue_from_id, :issue_to_id], :unique => true
11 end
11 end
12
12
13 def self.down
13 def self.down
14 remove_index :issue_relations, :column => [:issue_from_id, :issue_to_id]
14 remove_index :issue_relations, :column => [:issue_from_id, :issue_to_id]
15 end
15 end
16 end
16 end
@@ -1,516 +1,516
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 desc 'Mantis migration script'
18 desc 'Mantis migration script'
19
19
20 require 'active_record'
20 require 'active_record'
21 require 'pp'
21 require 'pp'
22
22
23 namespace :redmine do
23 namespace :redmine do
24 task :migrate_from_mantis => :environment do
24 task :migrate_from_mantis => :environment do
25
25
26 module MantisMigrate
26 module MantisMigrate
27
27
28 new_status = IssueStatus.find_by_position(1)
28 new_status = IssueStatus.find_by_position(1)
29 assigned_status = IssueStatus.find_by_position(2)
29 assigned_status = IssueStatus.find_by_position(2)
30 resolved_status = IssueStatus.find_by_position(3)
30 resolved_status = IssueStatus.find_by_position(3)
31 feedback_status = IssueStatus.find_by_position(4)
31 feedback_status = IssueStatus.find_by_position(4)
32 closed_status = IssueStatus.where(:is_closed => true).first
32 closed_status = IssueStatus.where(:is_closed => true).first
33 STATUS_MAPPING = {10 => new_status, # new
33 STATUS_MAPPING = {10 => new_status, # new
34 20 => feedback_status, # feedback
34 20 => feedback_status, # feedback
35 30 => new_status, # acknowledged
35 30 => new_status, # acknowledged
36 40 => new_status, # confirmed
36 40 => new_status, # confirmed
37 50 => assigned_status, # assigned
37 50 => assigned_status, # assigned
38 80 => resolved_status, # resolved
38 80 => resolved_status, # resolved
39 90 => closed_status # closed
39 90 => closed_status # closed
40 }
40 }
41
41
42 priorities = IssuePriority.all
42 priorities = IssuePriority.all
43 DEFAULT_PRIORITY = priorities[2]
43 DEFAULT_PRIORITY = priorities[2]
44 PRIORITY_MAPPING = {10 => priorities[1], # none
44 PRIORITY_MAPPING = {10 => priorities[1], # none
45 20 => priorities[1], # low
45 20 => priorities[1], # low
46 30 => priorities[2], # normal
46 30 => priorities[2], # normal
47 40 => priorities[3], # high
47 40 => priorities[3], # high
48 50 => priorities[4], # urgent
48 50 => priorities[4], # urgent
49 60 => priorities[5] # immediate
49 60 => priorities[5] # immediate
50 }
50 }
51
51
52 TRACKER_BUG = Tracker.find_by_position(1)
52 TRACKER_BUG = Tracker.find_by_position(1)
53 TRACKER_FEATURE = Tracker.find_by_position(2)
53 TRACKER_FEATURE = Tracker.find_by_position(2)
54
54
55 roles = Role.where(:builtin => 0).order('position ASC').all
55 roles = Role.where(:builtin => 0).order('position ASC').all
56 manager_role = roles[0]
56 manager_role = roles[0]
57 developer_role = roles[1]
57 developer_role = roles[1]
58 DEFAULT_ROLE = roles.last
58 DEFAULT_ROLE = roles.last
59 ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer
59 ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer
60 25 => DEFAULT_ROLE, # reporter
60 25 => DEFAULT_ROLE, # reporter
61 40 => DEFAULT_ROLE, # updater
61 40 => DEFAULT_ROLE, # updater
62 55 => developer_role, # developer
62 55 => developer_role, # developer
63 70 => manager_role, # manager
63 70 => manager_role, # manager
64 90 => manager_role # administrator
64 90 => manager_role # administrator
65 }
65 }
66
66
67 CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String
67 CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String
68 1 => 'int', # Numeric
68 1 => 'int', # Numeric
69 2 => 'int', # Float
69 2 => 'int', # Float
70 3 => 'list', # Enumeration
70 3 => 'list', # Enumeration
71 4 => 'string', # Email
71 4 => 'string', # Email
72 5 => 'bool', # Checkbox
72 5 => 'bool', # Checkbox
73 6 => 'list', # List
73 6 => 'list', # List
74 7 => 'list', # Multiselection list
74 7 => 'list', # Multiselection list
75 8 => 'date', # Date
75 8 => 'date', # Date
76 }
76 }
77
77
78 RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to
78 RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to
79 2 => IssueRelation::TYPE_RELATES, # parent of
79 2 => IssueRelation::TYPE_RELATES, # parent of
80 3 => IssueRelation::TYPE_RELATES, # child of
80 3 => IssueRelation::TYPE_RELATES, # child of
81 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of
81 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of
82 4 => IssueRelation::TYPE_DUPLICATES # has duplicate
82 4 => IssueRelation::TYPE_DUPLICATES # has duplicate
83 }
83 }
84
84
85 class MantisUser < ActiveRecord::Base
85 class MantisUser < ActiveRecord::Base
86 self.table_name = :mantis_user_table
86 self.table_name = :mantis_user_table
87
87
88 def firstname
88 def firstname
89 @firstname = realname.blank? ? username : realname.split.first[0..29]
89 @firstname = realname.blank? ? username : realname.split.first[0..29]
90 @firstname
90 @firstname
91 end
91 end
92
92
93 def lastname
93 def lastname
94 @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29]
94 @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29]
95 @lastname = '-' if @lastname.blank?
95 @lastname = '-' if @lastname.blank?
96 @lastname
96 @lastname
97 end
97 end
98
98
99 def email
99 def email
100 if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) &&
100 if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) &&
101 !User.find_by_mail(read_attribute(:email))
101 !User.find_by_mail(read_attribute(:email))
102 @email = read_attribute(:email)
102 @email = read_attribute(:email)
103 else
103 else
104 @email = "#{username}@foo.bar"
104 @email = "#{username}@foo.bar"
105 end
105 end
106 end
106 end
107
107
108 def username
108 def username
109 read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-')
109 read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-')
110 end
110 end
111 end
111 end
112
112
113 class MantisProject < ActiveRecord::Base
113 class MantisProject < ActiveRecord::Base
114 self.table_name = :mantis_project_table
114 self.table_name = :mantis_project_table
115 has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id
115 has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id
116 has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id
116 has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id
117 has_many :news, :class_name => "MantisNews", :foreign_key => :project_id
117 has_many :news, :class_name => "MantisNews", :foreign_key => :project_id
118 has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id
118 has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id
119
119
120 def identifier
120 def identifier
121 read_attribute(:name).downcase.gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH)
121 read_attribute(:name).downcase.gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH)
122 end
122 end
123 end
123 end
124
124
125 class MantisVersion < ActiveRecord::Base
125 class MantisVersion < ActiveRecord::Base
126 self.table_name = :mantis_project_version_table
126 self.table_name = :mantis_project_version_table
127
127
128 def version
128 def version
129 read_attribute(:version)[0..29]
129 read_attribute(:version)[0..29]
130 end
130 end
131
131
132 def description
132 def description
133 read_attribute(:description)[0..254]
133 read_attribute(:description)[0..254]
134 end
134 end
135 end
135 end
136
136
137 class MantisCategory < ActiveRecord::Base
137 class MantisCategory < ActiveRecord::Base
138 self.table_name = :mantis_project_category_table
138 self.table_name = :mantis_project_category_table
139 end
139 end
140
140
141 class MantisProjectUser < ActiveRecord::Base
141 class MantisProjectUser < ActiveRecord::Base
142 self.table_name = :mantis_project_user_list_table
142 self.table_name = :mantis_project_user_list_table
143 end
143 end
144
144
145 class MantisBug < ActiveRecord::Base
145 class MantisBug < ActiveRecord::Base
146 self.table_name = :mantis_bug_table
146 self.table_name = :mantis_bug_table
147 belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id
147 belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id
148 has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id
148 has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id
149 has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id
149 has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id
150 has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id
150 has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id
151 end
151 end
152
152
153 class MantisBugText < ActiveRecord::Base
153 class MantisBugText < ActiveRecord::Base
154 self.table_name = :mantis_bug_text_table
154 self.table_name = :mantis_bug_text_table
155
155
156 # Adds Mantis steps_to_reproduce and additional_information fields
156 # Adds Mantis steps_to_reproduce and additional_information fields
157 # to description if any
157 # to description if any
158 def full_description
158 def full_description
159 full_description = description
159 full_description = description
160 full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank?
160 full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank?
161 full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank?
161 full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank?
162 full_description
162 full_description
163 end
163 end
164 end
164 end
165
165
166 class MantisBugNote < ActiveRecord::Base
166 class MantisBugNote < ActiveRecord::Base
167 self.table_name = :mantis_bugnote_table
167 self.table_name = :mantis_bugnote_table
168 belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id
168 belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id
169 belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id
169 belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id
170 end
170 end
171
171
172 class MantisBugNoteText < ActiveRecord::Base
172 class MantisBugNoteText < ActiveRecord::Base
173 self.table_name = :mantis_bugnote_text_table
173 self.table_name = :mantis_bugnote_text_table
174 end
174 end
175
175
176 class MantisBugFile < ActiveRecord::Base
176 class MantisBugFile < ActiveRecord::Base
177 self.table_name = :mantis_bug_file_table
177 self.table_name = :mantis_bug_file_table
178
178
179 def size
179 def size
180 filesize
180 filesize
181 end
181 end
182
182
183 def original_filename
183 def original_filename
184 MantisMigrate.encode(filename)
184 MantisMigrate.encode(filename)
185 end
185 end
186
186
187 def content_type
187 def content_type
188 file_type
188 file_type
189 end
189 end
190
190
191 def read(*args)
191 def read(*args)
192 if @read_finished
192 if @read_finished
193 nil
193 nil
194 else
194 else
195 @read_finished = true
195 @read_finished = true
196 content
196 content
197 end
197 end
198 end
198 end
199 end
199 end
200
200
201 class MantisBugRelationship < ActiveRecord::Base
201 class MantisBugRelationship < ActiveRecord::Base
202 self.table_name = :mantis_bug_relationship_table
202 self.table_name = :mantis_bug_relationship_table
203 end
203 end
204
204
205 class MantisBugMonitor < ActiveRecord::Base
205 class MantisBugMonitor < ActiveRecord::Base
206 self.table_name = :mantis_bug_monitor_table
206 self.table_name = :mantis_bug_monitor_table
207 end
207 end
208
208
209 class MantisNews < ActiveRecord::Base
209 class MantisNews < ActiveRecord::Base
210 self.table_name = :mantis_news_table
210 self.table_name = :mantis_news_table
211 end
211 end
212
212
213 class MantisCustomField < ActiveRecord::Base
213 class MantisCustomField < ActiveRecord::Base
214 self.table_name = :mantis_custom_field_table
214 self.table_name = :mantis_custom_field_table
215 set_inheritance_column :none
215 set_inheritance_column :none
216 has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id
216 has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id
217 has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id
217 has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id
218
218
219 def format
219 def format
220 read_attribute :type
220 read_attribute :type
221 end
221 end
222
222
223 def name
223 def name
224 read_attribute(:name)[0..29]
224 read_attribute(:name)[0..29]
225 end
225 end
226 end
226 end
227
227
228 class MantisCustomFieldProject < ActiveRecord::Base
228 class MantisCustomFieldProject < ActiveRecord::Base
229 self.table_name = :mantis_custom_field_project_table
229 self.table_name = :mantis_custom_field_project_table
230 end
230 end
231
231
232 class MantisCustomFieldString < ActiveRecord::Base
232 class MantisCustomFieldString < ActiveRecord::Base
233 self.table_name = :mantis_custom_field_string_table
233 self.table_name = :mantis_custom_field_string_table
234 end
234 end
235
235
236 def self.migrate
236 def self.migrate
237
237
238 # Users
238 # Users
239 print "Migrating users"
239 print "Migrating users"
240 User.delete_all "login <> 'admin'"
240 User.where("login <> 'admin'").delete_all
241 users_map = {}
241 users_map = {}
242 users_migrated = 0
242 users_migrated = 0
243 MantisUser.all.each do |user|
243 MantisUser.all.each do |user|
244 u = User.new :firstname => encode(user.firstname),
244 u = User.new :firstname => encode(user.firstname),
245 :lastname => encode(user.lastname),
245 :lastname => encode(user.lastname),
246 :mail => user.email,
246 :mail => user.email,
247 :last_login_on => user.last_visit
247 :last_login_on => user.last_visit
248 u.login = user.username
248 u.login = user.username
249 u.password = 'mantis'
249 u.password = 'mantis'
250 u.status = User::STATUS_LOCKED if user.enabled != 1
250 u.status = User::STATUS_LOCKED if user.enabled != 1
251 u.admin = true if user.access_level == 90
251 u.admin = true if user.access_level == 90
252 next unless u.save!
252 next unless u.save!
253 users_migrated += 1
253 users_migrated += 1
254 users_map[user.id] = u.id
254 users_map[user.id] = u.id
255 print '.'
255 print '.'
256 end
256 end
257 puts
257 puts
258
258
259 # Projects
259 # Projects
260 print "Migrating projects"
260 print "Migrating projects"
261 Project.destroy_all
261 Project.destroy_all
262 projects_map = {}
262 projects_map = {}
263 versions_map = {}
263 versions_map = {}
264 categories_map = {}
264 categories_map = {}
265 MantisProject.all.each do |project|
265 MantisProject.all.each do |project|
266 p = Project.new :name => encode(project.name),
266 p = Project.new :name => encode(project.name),
267 :description => encode(project.description)
267 :description => encode(project.description)
268 p.identifier = project.identifier
268 p.identifier = project.identifier
269 next unless p.save
269 next unless p.save
270 projects_map[project.id] = p.id
270 projects_map[project.id] = p.id
271 p.enabled_module_names = ['issue_tracking', 'news', 'wiki']
271 p.enabled_module_names = ['issue_tracking', 'news', 'wiki']
272 p.trackers << TRACKER_BUG unless p.trackers.include?(TRACKER_BUG)
272 p.trackers << TRACKER_BUG unless p.trackers.include?(TRACKER_BUG)
273 p.trackers << TRACKER_FEATURE unless p.trackers.include?(TRACKER_FEATURE)
273 p.trackers << TRACKER_FEATURE unless p.trackers.include?(TRACKER_FEATURE)
274 print '.'
274 print '.'
275
275
276 # Project members
276 # Project members
277 project.members.each do |member|
277 project.members.each do |member|
278 m = Member.new :user => User.find_by_id(users_map[member.user_id]),
278 m = Member.new :user => User.find_by_id(users_map[member.user_id]),
279 :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE]
279 :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE]
280 m.project = p
280 m.project = p
281 m.save
281 m.save
282 end
282 end
283
283
284 # Project versions
284 # Project versions
285 project.versions.each do |version|
285 project.versions.each do |version|
286 v = Version.new :name => encode(version.version),
286 v = Version.new :name => encode(version.version),
287 :description => encode(version.description),
287 :description => encode(version.description),
288 :effective_date => (version.date_order ? version.date_order.to_date : nil)
288 :effective_date => (version.date_order ? version.date_order.to_date : nil)
289 v.project = p
289 v.project = p
290 v.save
290 v.save
291 versions_map[version.id] = v.id
291 versions_map[version.id] = v.id
292 end
292 end
293
293
294 # Project categories
294 # Project categories
295 project.categories.each do |category|
295 project.categories.each do |category|
296 g = IssueCategory.new :name => category.category[0,30]
296 g = IssueCategory.new :name => category.category[0,30]
297 g.project = p
297 g.project = p
298 g.save
298 g.save
299 categories_map[category.category] = g.id
299 categories_map[category.category] = g.id
300 end
300 end
301 end
301 end
302 puts
302 puts
303
303
304 # Bugs
304 # Bugs
305 print "Migrating bugs"
305 print "Migrating bugs"
306 Issue.destroy_all
306 Issue.destroy_all
307 issues_map = {}
307 issues_map = {}
308 keep_bug_ids = (Issue.count == 0)
308 keep_bug_ids = (Issue.count == 0)
309 MantisBug.find_each(:batch_size => 200) do |bug|
309 MantisBug.find_each(:batch_size => 200) do |bug|
310 next unless projects_map[bug.project_id] && users_map[bug.reporter_id]
310 next unless projects_map[bug.project_id] && users_map[bug.reporter_id]
311 i = Issue.new :project_id => projects_map[bug.project_id],
311 i = Issue.new :project_id => projects_map[bug.project_id],
312 :subject => encode(bug.summary),
312 :subject => encode(bug.summary),
313 :description => encode(bug.bug_text.full_description),
313 :description => encode(bug.bug_text.full_description),
314 :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY,
314 :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY,
315 :created_on => bug.date_submitted,
315 :created_on => bug.date_submitted,
316 :updated_on => bug.last_updated
316 :updated_on => bug.last_updated
317 i.author = User.find_by_id(users_map[bug.reporter_id])
317 i.author = User.find_by_id(users_map[bug.reporter_id])
318 i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank?
318 i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank?
319 i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank?
319 i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank?
320 i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG)
320 i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG)
321 i.status = STATUS_MAPPING[bug.status] || i.status
321 i.status = STATUS_MAPPING[bug.status] || i.status
322 i.id = bug.id if keep_bug_ids
322 i.id = bug.id if keep_bug_ids
323 next unless i.save
323 next unless i.save
324 issues_map[bug.id] = i.id
324 issues_map[bug.id] = i.id
325 print '.'
325 print '.'
326 STDOUT.flush
326 STDOUT.flush
327
327
328 # Assignee
328 # Assignee
329 # Redmine checks that the assignee is a project member
329 # Redmine checks that the assignee is a project member
330 if (bug.handler_id && users_map[bug.handler_id])
330 if (bug.handler_id && users_map[bug.handler_id])
331 i.assigned_to = User.find_by_id(users_map[bug.handler_id])
331 i.assigned_to = User.find_by_id(users_map[bug.handler_id])
332 i.save(:validate => false)
332 i.save(:validate => false)
333 end
333 end
334
334
335 # Bug notes
335 # Bug notes
336 bug.bug_notes.each do |note|
336 bug.bug_notes.each do |note|
337 next unless users_map[note.reporter_id]
337 next unless users_map[note.reporter_id]
338 n = Journal.new :notes => encode(note.bug_note_text.note),
338 n = Journal.new :notes => encode(note.bug_note_text.note),
339 :created_on => note.date_submitted
339 :created_on => note.date_submitted
340 n.user = User.find_by_id(users_map[note.reporter_id])
340 n.user = User.find_by_id(users_map[note.reporter_id])
341 n.journalized = i
341 n.journalized = i
342 n.save
342 n.save
343 end
343 end
344
344
345 # Bug files
345 # Bug files
346 bug.bug_files.each do |file|
346 bug.bug_files.each do |file|
347 a = Attachment.new :created_on => file.date_added
347 a = Attachment.new :created_on => file.date_added
348 a.file = file
348 a.file = file
349 a.author = User.first
349 a.author = User.first
350 a.container = i
350 a.container = i
351 a.save
351 a.save
352 end
352 end
353
353
354 # Bug monitors
354 # Bug monitors
355 bug.bug_monitors.each do |monitor|
355 bug.bug_monitors.each do |monitor|
356 next unless users_map[monitor.user_id]
356 next unless users_map[monitor.user_id]
357 i.add_watcher(User.find_by_id(users_map[monitor.user_id]))
357 i.add_watcher(User.find_by_id(users_map[monitor.user_id]))
358 end
358 end
359 end
359 end
360
360
361 # update issue id sequence if needed (postgresql)
361 # update issue id sequence if needed (postgresql)
362 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
362 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
363 puts
363 puts
364
364
365 # Bug relationships
365 # Bug relationships
366 print "Migrating bug relations"
366 print "Migrating bug relations"
367 MantisBugRelationship.all.each do |relation|
367 MantisBugRelationship.all.each do |relation|
368 next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id]
368 next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id]
369 r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type]
369 r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type]
370 r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id])
370 r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id])
371 r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id])
371 r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id])
372 pp r unless r.save
372 pp r unless r.save
373 print '.'
373 print '.'
374 STDOUT.flush
374 STDOUT.flush
375 end
375 end
376 puts
376 puts
377
377
378 # News
378 # News
379 print "Migrating news"
379 print "Migrating news"
380 News.destroy_all
380 News.destroy_all
381 MantisNews.where('project_id > 0').all.each do |news|
381 MantisNews.where('project_id > 0').all.each do |news|
382 next unless projects_map[news.project_id]
382 next unless projects_map[news.project_id]
383 n = News.new :project_id => projects_map[news.project_id],
383 n = News.new :project_id => projects_map[news.project_id],
384 :title => encode(news.headline[0..59]),
384 :title => encode(news.headline[0..59]),
385 :description => encode(news.body),
385 :description => encode(news.body),
386 :created_on => news.date_posted
386 :created_on => news.date_posted
387 n.author = User.find_by_id(users_map[news.poster_id])
387 n.author = User.find_by_id(users_map[news.poster_id])
388 n.save
388 n.save
389 print '.'
389 print '.'
390 STDOUT.flush
390 STDOUT.flush
391 end
391 end
392 puts
392 puts
393
393
394 # Custom fields
394 # Custom fields
395 print "Migrating custom fields"
395 print "Migrating custom fields"
396 IssueCustomField.destroy_all
396 IssueCustomField.destroy_all
397 MantisCustomField.all.each do |field|
397 MantisCustomField.all.each do |field|
398 f = IssueCustomField.new :name => field.name[0..29],
398 f = IssueCustomField.new :name => field.name[0..29],
399 :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format],
399 :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format],
400 :min_length => field.length_min,
400 :min_length => field.length_min,
401 :max_length => field.length_max,
401 :max_length => field.length_max,
402 :regexp => field.valid_regexp,
402 :regexp => field.valid_regexp,
403 :possible_values => field.possible_values.split('|'),
403 :possible_values => field.possible_values.split('|'),
404 :is_required => field.require_report?
404 :is_required => field.require_report?
405 next unless f.save
405 next unless f.save
406 print '.'
406 print '.'
407 STDOUT.flush
407 STDOUT.flush
408 # Trackers association
408 # Trackers association
409 f.trackers = Tracker.all
409 f.trackers = Tracker.all
410
410
411 # Projects association
411 # Projects association
412 field.projects.each do |project|
412 field.projects.each do |project|
413 f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id]
413 f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id]
414 end
414 end
415
415
416 # Values
416 # Values
417 field.values.each do |value|
417 field.values.each do |value|
418 v = CustomValue.new :custom_field_id => f.id,
418 v = CustomValue.new :custom_field_id => f.id,
419 :value => value.value
419 :value => value.value
420 v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id]
420 v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id]
421 v.save
421 v.save
422 end unless f.new_record?
422 end unless f.new_record?
423 end
423 end
424 puts
424 puts
425
425
426 puts
426 puts
427 puts "Users: #{users_migrated}/#{MantisUser.count}"
427 puts "Users: #{users_migrated}/#{MantisUser.count}"
428 puts "Projects: #{Project.count}/#{MantisProject.count}"
428 puts "Projects: #{Project.count}/#{MantisProject.count}"
429 puts "Memberships: #{Member.count}/#{MantisProjectUser.count}"
429 puts "Memberships: #{Member.count}/#{MantisProjectUser.count}"
430 puts "Versions: #{Version.count}/#{MantisVersion.count}"
430 puts "Versions: #{Version.count}/#{MantisVersion.count}"
431 puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}"
431 puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}"
432 puts "Bugs: #{Issue.count}/#{MantisBug.count}"
432 puts "Bugs: #{Issue.count}/#{MantisBug.count}"
433 puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}"
433 puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}"
434 puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}"
434 puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}"
435 puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}"
435 puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}"
436 puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}"
436 puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}"
437 puts "News: #{News.count}/#{MantisNews.count}"
437 puts "News: #{News.count}/#{MantisNews.count}"
438 puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}"
438 puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}"
439 end
439 end
440
440
441 def self.encoding(charset)
441 def self.encoding(charset)
442 @charset = charset
442 @charset = charset
443 end
443 end
444
444
445 def self.establish_connection(params)
445 def self.establish_connection(params)
446 constants.each do |const|
446 constants.each do |const|
447 klass = const_get(const)
447 klass = const_get(const)
448 next unless klass.respond_to? 'establish_connection'
448 next unless klass.respond_to? 'establish_connection'
449 klass.establish_connection params
449 klass.establish_connection params
450 end
450 end
451 end
451 end
452
452
453 def self.encode(text)
453 def self.encode(text)
454 text.to_s.force_encoding(@charset).encode('UTF-8')
454 text.to_s.force_encoding(@charset).encode('UTF-8')
455 end
455 end
456 end
456 end
457
457
458 puts
458 puts
459 if Redmine::DefaultData::Loader.no_data?
459 if Redmine::DefaultData::Loader.no_data?
460 puts "Redmine configuration need to be loaded before importing data."
460 puts "Redmine configuration need to be loaded before importing data."
461 puts "Please, run this first:"
461 puts "Please, run this first:"
462 puts
462 puts
463 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
463 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
464 exit
464 exit
465 end
465 end
466
466
467 puts "WARNING: Your Redmine data will be deleted during this process."
467 puts "WARNING: Your Redmine data will be deleted during this process."
468 print "Are you sure you want to continue ? [y/N] "
468 print "Are you sure you want to continue ? [y/N] "
469 STDOUT.flush
469 STDOUT.flush
470 break unless STDIN.gets.match(/^y$/i)
470 break unless STDIN.gets.match(/^y$/i)
471
471
472 # Default Mantis database settings
472 # Default Mantis database settings
473 db_params = {:adapter => 'mysql2',
473 db_params = {:adapter => 'mysql2',
474 :database => 'bugtracker',
474 :database => 'bugtracker',
475 :host => 'localhost',
475 :host => 'localhost',
476 :username => 'root',
476 :username => 'root',
477 :password => '' }
477 :password => '' }
478
478
479 puts
479 puts
480 puts "Please enter settings for your Mantis database"
480 puts "Please enter settings for your Mantis database"
481 [:adapter, :host, :database, :username, :password].each do |param|
481 [:adapter, :host, :database, :username, :password].each do |param|
482 print "#{param} [#{db_params[param]}]: "
482 print "#{param} [#{db_params[param]}]: "
483 value = STDIN.gets.chomp!
483 value = STDIN.gets.chomp!
484 db_params[param] = value unless value.blank?
484 db_params[param] = value unless value.blank?
485 end
485 end
486
486
487 while true
487 while true
488 print "encoding [UTF-8]: "
488 print "encoding [UTF-8]: "
489 STDOUT.flush
489 STDOUT.flush
490 encoding = STDIN.gets.chomp!
490 encoding = STDIN.gets.chomp!
491 encoding = 'UTF-8' if encoding.blank?
491 encoding = 'UTF-8' if encoding.blank?
492 break if MantisMigrate.encoding encoding
492 break if MantisMigrate.encoding encoding
493 puts "Invalid encoding!"
493 puts "Invalid encoding!"
494 end
494 end
495 puts
495 puts
496
496
497 # Make sure bugs can refer bugs in other projects
497 # Make sure bugs can refer bugs in other projects
498 Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations'
498 Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations'
499
499
500 old_notified_events = Setting.notified_events
500 old_notified_events = Setting.notified_events
501 old_password_min_length = Setting.password_min_length
501 old_password_min_length = Setting.password_min_length
502 begin
502 begin
503 # Turn off email notifications temporarily
503 # Turn off email notifications temporarily
504 Setting.notified_events = []
504 Setting.notified_events = []
505 Setting.password_min_length = 4
505 Setting.password_min_length = 4
506 # Run the migration
506 # Run the migration
507 MantisMigrate.establish_connection db_params
507 MantisMigrate.establish_connection db_params
508 MantisMigrate.migrate
508 MantisMigrate.migrate
509 ensure
509 ensure
510 # Restore previous settings
510 # Restore previous settings
511 Setting.notified_events = old_notified_events
511 Setting.notified_events = old_notified_events
512 Setting.password_min_length = old_password_min_length
512 Setting.password_min_length = old_password_min_length
513 end
513 end
514
514
515 end
515 end
516 end
516 end
@@ -1,168 +1,168
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class AdminControllerTest < Redmine::ControllerTest
20 class AdminControllerTest < Redmine::ControllerTest
21 fixtures :projects, :users, :email_addresses, :roles
21 fixtures :projects, :users, :email_addresses, :roles
22
22
23 def setup
23 def setup
24 User.current = nil
24 User.current = nil
25 @request.session[:user_id] = 1 # admin
25 @request.session[:user_id] = 1 # admin
26 end
26 end
27
27
28 def test_index
28 def test_index
29 get :index
29 get :index
30 assert_select 'div.nodata', 0
30 assert_select 'div.nodata', 0
31 end
31 end
32
32
33 def test_index_with_no_configuration_data
33 def test_index_with_no_configuration_data
34 delete_configuration_data
34 delete_configuration_data
35 get :index
35 get :index
36 assert_select 'div.nodata'
36 assert_select 'div.nodata'
37 end
37 end
38
38
39 def test_projects
39 def test_projects
40 get :projects
40 get :projects
41 assert_response :success
41 assert_response :success
42 assert_template 'projects'
42 assert_template 'projects'
43 assert_not_nil assigns(:projects)
43 assert_not_nil assigns(:projects)
44 # active projects only
44 # active projects only
45 assert_nil assigns(:projects).detect {|u| !u.active?}
45 assert_nil assigns(:projects).detect {|u| !u.active?}
46 end
46 end
47
47
48 def test_projects_with_status_filter
48 def test_projects_with_status_filter
49 get :projects, :status => 1
49 get :projects, :status => 1
50 assert_response :success
50 assert_response :success
51 assert_template 'projects'
51 assert_template 'projects'
52 assert_not_nil assigns(:projects)
52 assert_not_nil assigns(:projects)
53 # active projects only
53 # active projects only
54 assert_nil assigns(:projects).detect {|u| !u.active?}
54 assert_nil assigns(:projects).detect {|u| !u.active?}
55 end
55 end
56
56
57 def test_projects_with_name_filter
57 def test_projects_with_name_filter
58 get :projects, :name => 'store', :status => ''
58 get :projects, :name => 'store', :status => ''
59 assert_response :success
59 assert_response :success
60 assert_template 'projects'
60 assert_template 'projects'
61 projects = assigns(:projects)
61 projects = assigns(:projects)
62 assert_not_nil projects
62 assert_not_nil projects
63 assert_equal 1, projects.size
63 assert_equal 1, projects.size
64 assert_equal 'OnlineStore', projects.first.name
64 assert_equal 'OnlineStore', projects.first.name
65 end
65 end
66
66
67 def test_load_default_configuration_data
67 def test_load_default_configuration_data
68 delete_configuration_data
68 delete_configuration_data
69 post :default_configuration, :lang => 'fr'
69 post :default_configuration, :lang => 'fr'
70 assert_response :redirect
70 assert_response :redirect
71 assert_nil flash[:error]
71 assert_nil flash[:error]
72 assert IssueStatus.find_by_name('Nouveau')
72 assert IssueStatus.find_by_name('Nouveau')
73 end
73 end
74
74
75 def test_load_default_configuration_data_should_rescue_error
75 def test_load_default_configuration_data_should_rescue_error
76 delete_configuration_data
76 delete_configuration_data
77 Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong"))
77 Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong"))
78 post :default_configuration, :lang => 'fr'
78 post :default_configuration, :lang => 'fr'
79 assert_response :redirect
79 assert_response :redirect
80 assert_not_nil flash[:error]
80 assert_not_nil flash[:error]
81 assert_match /Something went wrong/, flash[:error]
81 assert_match /Something went wrong/, flash[:error]
82 end
82 end
83
83
84 def test_test_email
84 def test_test_email
85 user = User.find(1)
85 user = User.find(1)
86 user.pref.no_self_notified = '1'
86 user.pref.no_self_notified = '1'
87 user.pref.save!
87 user.pref.save!
88 ActionMailer::Base.deliveries.clear
88 ActionMailer::Base.deliveries.clear
89
89
90 post :test_email
90 post :test_email
91 assert_redirected_to '/settings?tab=notifications'
91 assert_redirected_to '/settings?tab=notifications'
92 mail = ActionMailer::Base.deliveries.last
92 mail = ActionMailer::Base.deliveries.last
93 assert_not_nil mail
93 assert_not_nil mail
94 user = User.find(1)
94 user = User.find(1)
95 assert_equal [user.mail], mail.bcc
95 assert_equal [user.mail], mail.bcc
96 end
96 end
97
97
98 def test_test_email_failure_should_display_the_error
98 def test_test_email_failure_should_display_the_error
99 Mailer.stubs(:test_email).raises(Exception, 'Some error message')
99 Mailer.stubs(:test_email).raises(Exception, 'Some error message')
100 post :test_email
100 post :test_email
101 assert_redirected_to '/settings?tab=notifications'
101 assert_redirected_to '/settings?tab=notifications'
102 assert_match /Some error message/, flash[:error]
102 assert_match /Some error message/, flash[:error]
103 end
103 end
104
104
105 def test_no_plugins
105 def test_no_plugins
106 Redmine::Plugin.stubs(:registered_plugins).returns({})
106 Redmine::Plugin.stubs(:registered_plugins).returns({})
107
107
108 get :plugins
108 get :plugins
109 assert_response :success
109 assert_response :success
110 assert_template 'plugins'
110 assert_template 'plugins'
111 assert_equal [], assigns(:plugins)
111 assert_equal [], assigns(:plugins)
112 end
112 end
113
113
114 def test_plugins
114 def test_plugins
115 # Register a few plugins
115 # Register a few plugins
116 Redmine::Plugin.register :foo do
116 Redmine::Plugin.register :foo do
117 name 'Foo plugin'
117 name 'Foo plugin'
118 author 'John Smith'
118 author 'John Smith'
119 description 'This is a test plugin'
119 description 'This is a test plugin'
120 version '0.0.1'
120 version '0.0.1'
121 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
121 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
122 end
122 end
123 Redmine::Plugin.register :bar do
123 Redmine::Plugin.register :bar do
124 end
124 end
125
125
126 get :plugins
126 get :plugins
127 assert_response :success
127 assert_response :success
128 assert_template 'plugins'
128 assert_template 'plugins'
129
129
130 assert_select 'tr#plugin-foo' do
130 assert_select 'tr#plugin-foo' do
131 assert_select 'td span.name', :text => 'Foo plugin'
131 assert_select 'td span.name', :text => 'Foo plugin'
132 assert_select 'td.configure a[href="/settings/plugin/foo"]'
132 assert_select 'td.configure a[href="/settings/plugin/foo"]'
133 end
133 end
134 assert_select 'tr#plugin-bar' do
134 assert_select 'tr#plugin-bar' do
135 assert_select 'td span.name', :text => 'Bar'
135 assert_select 'td span.name', :text => 'Bar'
136 assert_select 'td.configure a', 0
136 assert_select 'td.configure a', 0
137 end
137 end
138 end
138 end
139
139
140 def test_info
140 def test_info
141 get :info
141 get :info
142 assert_response :success
142 assert_response :success
143 assert_template 'info'
143 assert_template 'info'
144 end
144 end
145
145
146 def test_admin_menu_plugin_extension
146 def test_admin_menu_plugin_extension
147 Redmine::MenuManager.map :admin_menu do |menu|
147 Redmine::MenuManager.map :admin_menu do |menu|
148 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
148 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
149 end
149 end
150
150
151 get :index
151 get :index
152 assert_response :success
152 assert_response :success
153 assert_select 'div#admin-menu a[href="/foo/bar"]', :text => 'Test'
153 assert_select 'div#admin-menu a[href="/foo/bar"]', :text => 'Test'
154
154
155 Redmine::MenuManager.map :admin_menu do |menu|
155 Redmine::MenuManager.map :admin_menu do |menu|
156 menu.delete :test_admin_menu_plugin_extension
156 menu.delete :test_admin_menu_plugin_extension
157 end
157 end
158 end
158 end
159
159
160 private
160 private
161
161
162 def delete_configuration_data
162 def delete_configuration_data
163 Role.delete_all('builtin = 0')
163 Role.where('builtin = 0').delete_all
164 Tracker.delete_all
164 Tracker.delete_all
165 IssueStatus.delete_all
165 IssueStatus.delete_all
166 Enumeration.delete_all
166 Enumeration.delete_all
167 end
167 end
168 end
168 end
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,49 +1,49
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class DefaultDataTest < ActiveSupport::TestCase
20 class DefaultDataTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22 fixtures :roles
22 fixtures :roles
23
23
24 def test_no_data
24 def test_no_data
25 assert !Redmine::DefaultData::Loader::no_data?
25 assert !Redmine::DefaultData::Loader::no_data?
26 Role.delete_all("builtin = 0")
26 Role.where("builtin = 0").delete_all
27 Tracker.delete_all
27 Tracker.delete_all
28 IssueStatus.delete_all
28 IssueStatus.delete_all
29 Enumeration.delete_all
29 Enumeration.delete_all
30 assert Redmine::DefaultData::Loader::no_data?
30 assert Redmine::DefaultData::Loader::no_data?
31 end
31 end
32
32
33 def test_load
33 def test_load
34 valid_languages.each do |lang|
34 valid_languages.each do |lang|
35 begin
35 begin
36 Role.delete_all("builtin = 0")
36 Role.where("builtin = 0").delete_all
37 Tracker.delete_all
37 Tracker.delete_all
38 IssueStatus.delete_all
38 IssueStatus.delete_all
39 Enumeration.delete_all
39 Enumeration.delete_all
40 assert Redmine::DefaultData::Loader::load(lang)
40 assert Redmine::DefaultData::Loader::load(lang)
41 assert_not_nil DocumentCategory.first
41 assert_not_nil DocumentCategory.first
42 assert_not_nil IssuePriority.first
42 assert_not_nil IssuePriority.first
43 assert_not_nil TimeEntryActivity.first
43 assert_not_nil TimeEntryActivity.first
44 rescue ActiveRecord::RecordInvalid => e
44 rescue ActiveRecord::RecordInvalid => e
45 assert false, ":#{lang} default data is invalid (#{e.message})."
45 assert false, ":#{lang} default data is invalid (#{e.message})."
46 end
46 end
47 end
47 end
48 end
48 end
49 end
49 end
@@ -1,90 +1,90
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class NewsTest < ActiveSupport::TestCase
20 class NewsTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :news
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules, :news
22
22
23 def valid_news
23 def valid_news
24 { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.first }
24 { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.first }
25 end
25 end
26
26
27 def setup
27 def setup
28 end
28 end
29
29
30 def test_create_should_send_email_notification
30 def test_create_should_send_email_notification
31 ActionMailer::Base.deliveries.clear
31 ActionMailer::Base.deliveries.clear
32 news = Project.find(1).news.new(valid_news)
32 news = Project.find(1).news.new(valid_news)
33
33
34 with_settings :notified_events => %w(news_added) do
34 with_settings :notified_events => %w(news_added) do
35 assert news.save
35 assert news.save
36 end
36 end
37 assert_equal 1, ActionMailer::Base.deliveries.size
37 assert_equal 1, ActionMailer::Base.deliveries.size
38 end
38 end
39
39
40 def test_should_include_news_for_projects_with_news_enabled
40 def test_should_include_news_for_projects_with_news_enabled
41 project = projects(:projects_001)
41 project = projects(:projects_001)
42 assert project.enabled_modules.any?{ |em| em.name == 'news' }
42 assert project.enabled_modules.any?{ |em| em.name == 'news' }
43
43
44 # News.latest should return news from projects_001
44 # News.latest should return news from projects_001
45 assert News.latest.any? { |news| news.project == project }
45 assert News.latest.any? { |news| news.project == project }
46 end
46 end
47
47
48 def test_should_not_include_news_for_projects_with_news_disabled
48 def test_should_not_include_news_for_projects_with_news_disabled
49 EnabledModule.delete_all(["project_id = ? AND name = ?", 2, 'news'])
49 EnabledModule.where(["project_id = ? AND name = ?", 2, 'news']).delete_all
50 project = Project.find(2)
50 project = Project.find(2)
51
51
52 # Add a piece of news to the project
52 # Add a piece of news to the project
53 news = project.news.create(valid_news)
53 news = project.news.create(valid_news)
54
54
55 # News.latest should not return that new piece of news
55 # News.latest should not return that new piece of news
56 assert News.latest.include?(news) == false
56 assert News.latest.include?(news) == false
57 end
57 end
58
58
59 def test_should_only_include_news_from_projects_visibly_to_the_user
59 def test_should_only_include_news_from_projects_visibly_to_the_user
60 assert News.latest(User.anonymous).all? { |news| news.project.is_public? }
60 assert News.latest(User.anonymous).all? { |news| news.project.is_public? }
61 end
61 end
62
62
63 def test_should_limit_the_amount_of_returned_news
63 def test_should_limit_the_amount_of_returned_news
64 # Make sure we have a bunch of news stories
64 # Make sure we have a bunch of news stories
65 10.times { projects(:projects_001).news.create(valid_news) }
65 10.times { projects(:projects_001).news.create(valid_news) }
66 assert_equal 2, News.latest(users(:users_002), 2).size
66 assert_equal 2, News.latest(users(:users_002), 2).size
67 assert_equal 6, News.latest(users(:users_002), 6).size
67 assert_equal 6, News.latest(users(:users_002), 6).size
68 end
68 end
69
69
70 def test_should_return_5_news_stories_by_default
70 def test_should_return_5_news_stories_by_default
71 # Make sure we have a bunch of news stories
71 # Make sure we have a bunch of news stories
72 10.times { projects(:projects_001).news.create(valid_news) }
72 10.times { projects(:projects_001).news.create(valid_news) }
73 assert_equal 5, News.latest(users(:users_004)).size
73 assert_equal 5, News.latest(users(:users_004)).size
74 end
74 end
75
75
76 def test_attachments_should_be_visible
76 def test_attachments_should_be_visible
77 assert News.find(1).attachments_visible?(User.anonymous)
77 assert News.find(1).attachments_visible?(User.anonymous)
78 end
78 end
79
79
80 def test_attachments_should_be_deletable_with_manage_news_permission
80 def test_attachments_should_be_deletable_with_manage_news_permission
81 manager = User.find(2)
81 manager = User.find(2)
82 assert News.find(1).attachments_deletable?(manager)
82 assert News.find(1).attachments_deletable?(manager)
83 end
83 end
84
84
85 def test_attachments_should_not_be_deletable_without_manage_news_permission
85 def test_attachments_should_not_be_deletable_without_manage_news_permission
86 manager = User.find(2)
86 manager = User.find(2)
87 Role.find_by_name('Manager').remove_permission!(:manage_news)
87 Role.find_by_name('Manager').remove_permission!(:manage_news)
88 assert !News.find(1).attachments_deletable?(manager)
88 assert !News.find(1).attachments_deletable?(manager)
89 end
89 end
90 end
90 end
@@ -1,1237 +1,1237
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources,
21 fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources,
22 :trackers, :issue_statuses,
22 :trackers, :issue_statuses,
23 :projects_trackers,
23 :projects_trackers,
24 :watchers,
24 :watchers,
25 :issue_categories, :enumerations, :issues,
25 :issue_categories, :enumerations, :issues,
26 :journals, :journal_details,
26 :journals, :journal_details,
27 :groups_users,
27 :groups_users,
28 :enabled_modules,
28 :enabled_modules,
29 :tokens
29 :tokens
30
30
31 include Redmine::I18n
31 include Redmine::I18n
32
32
33 def setup
33 def setup
34 @admin = User.find(1)
34 @admin = User.find(1)
35 @jsmith = User.find(2)
35 @jsmith = User.find(2)
36 @dlopper = User.find(3)
36 @dlopper = User.find(3)
37 end
37 end
38
38
39 def test_sorted_scope_should_sort_user_by_display_name
39 def test_sorted_scope_should_sort_user_by_display_name
40 # Use .active to ignore anonymous with localized display name
40 # Use .active to ignore anonymous with localized display name
41 assert_equal User.active.map(&:name).map(&:downcase).sort,
41 assert_equal User.active.map(&:name).map(&:downcase).sort,
42 User.active.sorted.map(&:name).map(&:downcase)
42 User.active.sorted.map(&:name).map(&:downcase)
43 end
43 end
44
44
45 def test_generate
45 def test_generate
46 User.generate!(:firstname => 'Testing connection')
46 User.generate!(:firstname => 'Testing connection')
47 User.generate!(:firstname => 'Testing connection')
47 User.generate!(:firstname => 'Testing connection')
48 assert_equal 2, User.where(:firstname => 'Testing connection').count
48 assert_equal 2, User.where(:firstname => 'Testing connection').count
49 end
49 end
50
50
51 def test_truth
51 def test_truth
52 assert_kind_of User, @jsmith
52 assert_kind_of User, @jsmith
53 end
53 end
54
54
55 def test_should_validate_status
55 def test_should_validate_status
56 user = User.new
56 user = User.new
57 user.status = 0
57 user.status = 0
58
58
59 assert !user.save
59 assert !user.save
60 assert_include I18n.translate('activerecord.errors.messages.invalid'), user.errors[:status]
60 assert_include I18n.translate('activerecord.errors.messages.invalid'), user.errors[:status]
61 end
61 end
62
62
63 def test_mail_should_be_stripped
63 def test_mail_should_be_stripped
64 u = User.new
64 u = User.new
65 u.mail = " foo@bar.com "
65 u.mail = " foo@bar.com "
66 assert_equal "foo@bar.com", u.mail
66 assert_equal "foo@bar.com", u.mail
67 end
67 end
68
68
69 def test_should_create_email_address
69 def test_should_create_email_address
70 u = User.new(:firstname => "new", :lastname => "user")
70 u = User.new(:firstname => "new", :lastname => "user")
71 u.login = "create_email_address"
71 u.login = "create_email_address"
72 u.mail = "defaultemail@somenet.foo"
72 u.mail = "defaultemail@somenet.foo"
73 assert u.save
73 assert u.save
74 u.reload
74 u.reload
75 assert u.email_address
75 assert u.email_address
76 assert_equal "defaultemail@somenet.foo", u.email_address.address
76 assert_equal "defaultemail@somenet.foo", u.email_address.address
77 assert_equal true, u.email_address.is_default
77 assert_equal true, u.email_address.is_default
78 assert_equal true, u.email_address.notify
78 assert_equal true, u.email_address.notify
79 end
79 end
80
80
81 def test_should_not_create_user_without_mail
81 def test_should_not_create_user_without_mail
82 set_language_if_valid 'en'
82 set_language_if_valid 'en'
83 u = User.new(:firstname => "new", :lastname => "user")
83 u = User.new(:firstname => "new", :lastname => "user")
84 u.login = "user_without_mail"
84 u.login = "user_without_mail"
85 assert !u.save
85 assert !u.save
86 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
86 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
87 end
87 end
88
88
89 def test_should_not_create_user_with_blank_mail
89 def test_should_not_create_user_with_blank_mail
90 set_language_if_valid 'en'
90 set_language_if_valid 'en'
91 u = User.new(:firstname => "new", :lastname => "user")
91 u = User.new(:firstname => "new", :lastname => "user")
92 u.login = "user_with_blank_mail"
92 u.login = "user_with_blank_mail"
93 u.mail = ''
93 u.mail = ''
94 assert !u.save
94 assert !u.save
95 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
95 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
96 end
96 end
97
97
98 def test_should_not_update_user_with_blank_mail
98 def test_should_not_update_user_with_blank_mail
99 set_language_if_valid 'en'
99 set_language_if_valid 'en'
100 u = User.find(2)
100 u = User.find(2)
101 u.mail = ''
101 u.mail = ''
102 assert !u.save
102 assert !u.save
103 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
103 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
104 end
104 end
105
105
106 def test_login_length_validation
106 def test_login_length_validation
107 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
107 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
108 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
108 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
109 assert !user.valid?
109 assert !user.valid?
110
110
111 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
111 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
112 assert user.valid?
112 assert user.valid?
113 assert user.save
113 assert user.save
114 end
114 end
115
115
116 def test_generate_password_should_respect_minimum_password_length
116 def test_generate_password_should_respect_minimum_password_length
117 with_settings :password_min_length => 15 do
117 with_settings :password_min_length => 15 do
118 user = User.generate!(:generate_password => true)
118 user = User.generate!(:generate_password => true)
119 assert user.password.length >= 15
119 assert user.password.length >= 15
120 end
120 end
121 end
121 end
122
122
123 def test_generate_password_should_not_generate_password_with_less_than_10_characters
123 def test_generate_password_should_not_generate_password_with_less_than_10_characters
124 with_settings :password_min_length => 4 do
124 with_settings :password_min_length => 4 do
125 user = User.generate!(:generate_password => true)
125 user = User.generate!(:generate_password => true)
126 assert user.password.length >= 10
126 assert user.password.length >= 10
127 end
127 end
128 end
128 end
129
129
130 def test_generate_password_on_create_should_set_password
130 def test_generate_password_on_create_should_set_password
131 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
131 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
132 user.login = "newuser"
132 user.login = "newuser"
133 user.generate_password = true
133 user.generate_password = true
134 assert user.save
134 assert user.save
135
135
136 password = user.password
136 password = user.password
137 assert user.check_password?(password)
137 assert user.check_password?(password)
138 end
138 end
139
139
140 def test_generate_password_on_update_should_update_password
140 def test_generate_password_on_update_should_update_password
141 user = User.find(2)
141 user = User.find(2)
142 hash = user.hashed_password
142 hash = user.hashed_password
143 user.generate_password = true
143 user.generate_password = true
144 assert user.save
144 assert user.save
145
145
146 password = user.password
146 password = user.password
147 assert user.check_password?(password)
147 assert user.check_password?(password)
148 assert_not_equal hash, user.reload.hashed_password
148 assert_not_equal hash, user.reload.hashed_password
149 end
149 end
150
150
151 def test_create
151 def test_create
152 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
152 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
153
153
154 user.login = "jsmith"
154 user.login = "jsmith"
155 user.password, user.password_confirmation = "password", "password"
155 user.password, user.password_confirmation = "password", "password"
156 # login uniqueness
156 # login uniqueness
157 assert !user.save
157 assert !user.save
158 assert_equal 1, user.errors.count
158 assert_equal 1, user.errors.count
159
159
160 user.login = "newuser"
160 user.login = "newuser"
161 user.password, user.password_confirmation = "password", "pass"
161 user.password, user.password_confirmation = "password", "pass"
162 # password confirmation
162 # password confirmation
163 assert !user.save
163 assert !user.save
164 assert_equal 1, user.errors.count
164 assert_equal 1, user.errors.count
165
165
166 user.password, user.password_confirmation = "password", "password"
166 user.password, user.password_confirmation = "password", "password"
167 assert user.save
167 assert user.save
168 end
168 end
169
169
170 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
170 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
171 @user1 = User.generate!
171 @user1 = User.generate!
172 assert_equal 'only_my_events', @user1.mail_notification
172 assert_equal 'only_my_events', @user1.mail_notification
173 with_settings :default_notification_option => 'all' do
173 with_settings :default_notification_option => 'all' do
174 @user2 = User.generate!
174 @user2 = User.generate!
175 assert_equal 'all', @user2.mail_notification
175 assert_equal 'all', @user2.mail_notification
176 end
176 end
177 end
177 end
178
178
179 def test_user_login_should_be_case_insensitive
179 def test_user_login_should_be_case_insensitive
180 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
180 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
181 u.login = 'newuser'
181 u.login = 'newuser'
182 u.password, u.password_confirmation = "password", "password"
182 u.password, u.password_confirmation = "password", "password"
183 assert u.save
183 assert u.save
184 u = User.new(:firstname => "Similar", :lastname => "User",
184 u = User.new(:firstname => "Similar", :lastname => "User",
185 :mail => "similaruser@somenet.foo")
185 :mail => "similaruser@somenet.foo")
186 u.login = 'NewUser'
186 u.login = 'NewUser'
187 u.password, u.password_confirmation = "password", "password"
187 u.password, u.password_confirmation = "password", "password"
188 assert !u.save
188 assert !u.save
189 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
189 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
190 end
190 end
191
191
192 def test_mail_uniqueness_should_not_be_case_sensitive
192 def test_mail_uniqueness_should_not_be_case_sensitive
193 set_language_if_valid 'en'
193 set_language_if_valid 'en'
194 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
194 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
195 u.login = 'newuser1'
195 u.login = 'newuser1'
196 u.password, u.password_confirmation = "password", "password"
196 u.password, u.password_confirmation = "password", "password"
197 assert u.save
197 assert u.save
198
198
199 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
199 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
200 u.login = 'newuser2'
200 u.login = 'newuser2'
201 u.password, u.password_confirmation = "password", "password"
201 u.password, u.password_confirmation = "password", "password"
202 assert !u.save
202 assert !u.save
203 assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages
203 assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages
204 end
204 end
205
205
206 def test_update
206 def test_update
207 assert_equal "admin", @admin.login
207 assert_equal "admin", @admin.login
208 @admin.login = "john"
208 @admin.login = "john"
209 assert @admin.save, @admin.errors.full_messages.join("; ")
209 assert @admin.save, @admin.errors.full_messages.join("; ")
210 @admin.reload
210 @admin.reload
211 assert_equal "john", @admin.login
211 assert_equal "john", @admin.login
212 end
212 end
213
213
214 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
214 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
215 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
215 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
216 u1.login = 'newuser1'
216 u1.login = 'newuser1'
217 assert u1.save
217 assert u1.save
218
218
219 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
219 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
220 u2.login = 'newuser1'
220 u2.login = 'newuser1'
221 assert u2.save(:validate => false)
221 assert u2.save(:validate => false)
222
222
223 user = User.find(u2.id)
223 user = User.find(u2.id)
224 user.firstname = "firstname"
224 user.firstname = "firstname"
225 assert user.save, "Save failed"
225 assert user.save, "Save failed"
226 end
226 end
227
227
228 def test_destroy_should_delete_members_and_roles
228 def test_destroy_should_delete_members_and_roles
229 members = Member.where(:user_id => 2)
229 members = Member.where(:user_id => 2)
230 ms = members.count
230 ms = members.count
231 rs = members.collect(&:roles).flatten.size
231 rs = members.collect(&:roles).flatten.size
232 assert ms > 0
232 assert ms > 0
233 assert rs > 0
233 assert rs > 0
234 assert_difference 'Member.count', - ms do
234 assert_difference 'Member.count', - ms do
235 assert_difference 'MemberRole.count', - rs do
235 assert_difference 'MemberRole.count', - rs do
236 User.find(2).destroy
236 User.find(2).destroy
237 end
237 end
238 end
238 end
239 assert_nil User.find_by_id(2)
239 assert_nil User.find_by_id(2)
240 assert_equal 0, Member.where(:user_id => 2).count
240 assert_equal 0, Member.where(:user_id => 2).count
241 end
241 end
242
242
243 def test_destroy_should_update_attachments
243 def test_destroy_should_update_attachments
244 attachment = Attachment.create!(:container => Project.find(1),
244 attachment = Attachment.create!(:container => Project.find(1),
245 :file => uploaded_test_file("testfile.txt", "text/plain"),
245 :file => uploaded_test_file("testfile.txt", "text/plain"),
246 :author_id => 2)
246 :author_id => 2)
247
247
248 User.find(2).destroy
248 User.find(2).destroy
249 assert_nil User.find_by_id(2)
249 assert_nil User.find_by_id(2)
250 assert_equal User.anonymous, attachment.reload.author
250 assert_equal User.anonymous, attachment.reload.author
251 end
251 end
252
252
253 def test_destroy_should_update_comments
253 def test_destroy_should_update_comments
254 comment = Comment.create!(
254 comment = Comment.create!(
255 :commented => News.create!(:project_id => 1,
255 :commented => News.create!(:project_id => 1,
256 :author_id => 1, :title => 'foo', :description => 'foo'),
256 :author_id => 1, :title => 'foo', :description => 'foo'),
257 :author => User.find(2),
257 :author => User.find(2),
258 :comments => 'foo'
258 :comments => 'foo'
259 )
259 )
260
260
261 User.find(2).destroy
261 User.find(2).destroy
262 assert_nil User.find_by_id(2)
262 assert_nil User.find_by_id(2)
263 assert_equal User.anonymous, comment.reload.author
263 assert_equal User.anonymous, comment.reload.author
264 end
264 end
265
265
266 def test_destroy_should_update_issues
266 def test_destroy_should_update_issues
267 issue = Issue.create!(:project_id => 1, :author_id => 2,
267 issue = Issue.create!(:project_id => 1, :author_id => 2,
268 :tracker_id => 1, :subject => 'foo')
268 :tracker_id => 1, :subject => 'foo')
269
269
270 User.find(2).destroy
270 User.find(2).destroy
271 assert_nil User.find_by_id(2)
271 assert_nil User.find_by_id(2)
272 assert_equal User.anonymous, issue.reload.author
272 assert_equal User.anonymous, issue.reload.author
273 end
273 end
274
274
275 def test_destroy_should_unassign_issues
275 def test_destroy_should_unassign_issues
276 issue = Issue.create!(:project_id => 1, :author_id => 1,
276 issue = Issue.create!(:project_id => 1, :author_id => 1,
277 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
277 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
278
278
279 User.find(2).destroy
279 User.find(2).destroy
280 assert_nil User.find_by_id(2)
280 assert_nil User.find_by_id(2)
281 assert_nil issue.reload.assigned_to
281 assert_nil issue.reload.assigned_to
282 end
282 end
283
283
284 def test_destroy_should_update_journals
284 def test_destroy_should_update_journals
285 issue = Issue.create!(:project_id => 1, :author_id => 2,
285 issue = Issue.create!(:project_id => 1, :author_id => 2,
286 :tracker_id => 1, :subject => 'foo')
286 :tracker_id => 1, :subject => 'foo')
287 issue.init_journal(User.find(2), "update")
287 issue.init_journal(User.find(2), "update")
288 issue.save!
288 issue.save!
289
289
290 User.find(2).destroy
290 User.find(2).destroy
291 assert_nil User.find_by_id(2)
291 assert_nil User.find_by_id(2)
292 assert_equal User.anonymous, issue.journals.first.reload.user
292 assert_equal User.anonymous, issue.journals.first.reload.user
293 end
293 end
294
294
295 def test_destroy_should_update_journal_details_old_value
295 def test_destroy_should_update_journal_details_old_value
296 issue = Issue.create!(:project_id => 1, :author_id => 1,
296 issue = Issue.create!(:project_id => 1, :author_id => 1,
297 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
297 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
298 issue.init_journal(User.find(1), "update")
298 issue.init_journal(User.find(1), "update")
299 issue.assigned_to_id = nil
299 issue.assigned_to_id = nil
300 assert_difference 'JournalDetail.count' do
300 assert_difference 'JournalDetail.count' do
301 issue.save!
301 issue.save!
302 end
302 end
303 journal_detail = JournalDetail.order('id DESC').first
303 journal_detail = JournalDetail.order('id DESC').first
304 assert_equal '2', journal_detail.old_value
304 assert_equal '2', journal_detail.old_value
305
305
306 User.find(2).destroy
306 User.find(2).destroy
307 assert_nil User.find_by_id(2)
307 assert_nil User.find_by_id(2)
308 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
308 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
309 end
309 end
310
310
311 def test_destroy_should_update_journal_details_value
311 def test_destroy_should_update_journal_details_value
312 issue = Issue.create!(:project_id => 1, :author_id => 1,
312 issue = Issue.create!(:project_id => 1, :author_id => 1,
313 :tracker_id => 1, :subject => 'foo')
313 :tracker_id => 1, :subject => 'foo')
314 issue.init_journal(User.find(1), "update")
314 issue.init_journal(User.find(1), "update")
315 issue.assigned_to_id = 2
315 issue.assigned_to_id = 2
316 assert_difference 'JournalDetail.count' do
316 assert_difference 'JournalDetail.count' do
317 issue.save!
317 issue.save!
318 end
318 end
319 journal_detail = JournalDetail.order('id DESC').first
319 journal_detail = JournalDetail.order('id DESC').first
320 assert_equal '2', journal_detail.value
320 assert_equal '2', journal_detail.value
321
321
322 User.find(2).destroy
322 User.find(2).destroy
323 assert_nil User.find_by_id(2)
323 assert_nil User.find_by_id(2)
324 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
324 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
325 end
325 end
326
326
327 def test_destroy_should_update_messages
327 def test_destroy_should_update_messages
328 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
328 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
329 message = Message.create!(:board_id => board.id, :author_id => 2,
329 message = Message.create!(:board_id => board.id, :author_id => 2,
330 :subject => 'foo', :content => 'foo')
330 :subject => 'foo', :content => 'foo')
331 User.find(2).destroy
331 User.find(2).destroy
332 assert_nil User.find_by_id(2)
332 assert_nil User.find_by_id(2)
333 assert_equal User.anonymous, message.reload.author
333 assert_equal User.anonymous, message.reload.author
334 end
334 end
335
335
336 def test_destroy_should_update_news
336 def test_destroy_should_update_news
337 news = News.create!(:project_id => 1, :author_id => 2,
337 news = News.create!(:project_id => 1, :author_id => 2,
338 :title => 'foo', :description => 'foo')
338 :title => 'foo', :description => 'foo')
339 User.find(2).destroy
339 User.find(2).destroy
340 assert_nil User.find_by_id(2)
340 assert_nil User.find_by_id(2)
341 assert_equal User.anonymous, news.reload.author
341 assert_equal User.anonymous, news.reload.author
342 end
342 end
343
343
344 def test_destroy_should_delete_private_queries
344 def test_destroy_should_delete_private_queries
345 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE)
345 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE)
346 query.project_id = 1
346 query.project_id = 1
347 query.user_id = 2
347 query.user_id = 2
348 query.save!
348 query.save!
349
349
350 User.find(2).destroy
350 User.find(2).destroy
351 assert_nil User.find_by_id(2)
351 assert_nil User.find_by_id(2)
352 assert_nil Query.find_by_id(query.id)
352 assert_nil Query.find_by_id(query.id)
353 end
353 end
354
354
355 def test_destroy_should_update_public_queries
355 def test_destroy_should_update_public_queries
356 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC)
356 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC)
357 query.project_id = 1
357 query.project_id = 1
358 query.user_id = 2
358 query.user_id = 2
359 query.save!
359 query.save!
360
360
361 User.find(2).destroy
361 User.find(2).destroy
362 assert_nil User.find_by_id(2)
362 assert_nil User.find_by_id(2)
363 assert_equal User.anonymous, query.reload.user
363 assert_equal User.anonymous, query.reload.user
364 end
364 end
365
365
366 def test_destroy_should_update_time_entries
366 def test_destroy_should_update_time_entries
367 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today,
367 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today,
368 :activity => TimeEntryActivity.create!(:name => 'foo'))
368 :activity => TimeEntryActivity.create!(:name => 'foo'))
369 entry.project_id = 1
369 entry.project_id = 1
370 entry.user_id = 2
370 entry.user_id = 2
371 entry.save!
371 entry.save!
372
372
373 User.find(2).destroy
373 User.find(2).destroy
374 assert_nil User.find_by_id(2)
374 assert_nil User.find_by_id(2)
375 assert_equal User.anonymous, entry.reload.user
375 assert_equal User.anonymous, entry.reload.user
376 end
376 end
377
377
378 def test_destroy_should_delete_tokens
378 def test_destroy_should_delete_tokens
379 token = Token.create!(:user_id => 2, :value => 'foo')
379 token = Token.create!(:user_id => 2, :value => 'foo')
380
380
381 User.find(2).destroy
381 User.find(2).destroy
382 assert_nil User.find_by_id(2)
382 assert_nil User.find_by_id(2)
383 assert_nil Token.find_by_id(token.id)
383 assert_nil Token.find_by_id(token.id)
384 end
384 end
385
385
386 def test_destroy_should_delete_watchers
386 def test_destroy_should_delete_watchers
387 issue = Issue.create!(:project_id => 1, :author_id => 1,
387 issue = Issue.create!(:project_id => 1, :author_id => 1,
388 :tracker_id => 1, :subject => 'foo')
388 :tracker_id => 1, :subject => 'foo')
389 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
389 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
390
390
391 User.find(2).destroy
391 User.find(2).destroy
392 assert_nil User.find_by_id(2)
392 assert_nil User.find_by_id(2)
393 assert_nil Watcher.find_by_id(watcher.id)
393 assert_nil Watcher.find_by_id(watcher.id)
394 end
394 end
395
395
396 def test_destroy_should_update_wiki_contents
396 def test_destroy_should_update_wiki_contents
397 wiki_content = WikiContent.create!(
397 wiki_content = WikiContent.create!(
398 :text => 'foo',
398 :text => 'foo',
399 :author_id => 2,
399 :author_id => 2,
400 :page => WikiPage.create!(:title => 'Foo',
400 :page => WikiPage.create!(:title => 'Foo',
401 :wiki => Wiki.create!(:project_id => 3,
401 :wiki => Wiki.create!(:project_id => 3,
402 :start_page => 'Start'))
402 :start_page => 'Start'))
403 )
403 )
404 wiki_content.text = 'bar'
404 wiki_content.text = 'bar'
405 assert_difference 'WikiContent::Version.count' do
405 assert_difference 'WikiContent::Version.count' do
406 wiki_content.save!
406 wiki_content.save!
407 end
407 end
408
408
409 User.find(2).destroy
409 User.find(2).destroy
410 assert_nil User.find_by_id(2)
410 assert_nil User.find_by_id(2)
411 assert_equal User.anonymous, wiki_content.reload.author
411 assert_equal User.anonymous, wiki_content.reload.author
412 wiki_content.versions.each do |version|
412 wiki_content.versions.each do |version|
413 assert_equal User.anonymous, version.reload.author
413 assert_equal User.anonymous, version.reload.author
414 end
414 end
415 end
415 end
416
416
417 def test_destroy_should_nullify_issue_categories
417 def test_destroy_should_nullify_issue_categories
418 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
418 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
419
419
420 User.find(2).destroy
420 User.find(2).destroy
421 assert_nil User.find_by_id(2)
421 assert_nil User.find_by_id(2)
422 assert_nil category.reload.assigned_to_id
422 assert_nil category.reload.assigned_to_id
423 end
423 end
424
424
425 def test_destroy_should_nullify_changesets
425 def test_destroy_should_nullify_changesets
426 changeset = Changeset.create!(
426 changeset = Changeset.create!(
427 :repository => Repository::Subversion.create!(
427 :repository => Repository::Subversion.create!(
428 :project_id => 1,
428 :project_id => 1,
429 :url => 'file:///tmp',
429 :url => 'file:///tmp',
430 :identifier => 'tmp'
430 :identifier => 'tmp'
431 ),
431 ),
432 :revision => '12',
432 :revision => '12',
433 :committed_on => Time.now,
433 :committed_on => Time.now,
434 :committer => 'jsmith'
434 :committer => 'jsmith'
435 )
435 )
436 assert_equal 2, changeset.user_id
436 assert_equal 2, changeset.user_id
437
437
438 User.find(2).destroy
438 User.find(2).destroy
439 assert_nil User.find_by_id(2)
439 assert_nil User.find_by_id(2)
440 assert_nil changeset.reload.user_id
440 assert_nil changeset.reload.user_id
441 end
441 end
442
442
443 def test_anonymous_user_should_not_be_destroyable
443 def test_anonymous_user_should_not_be_destroyable
444 assert_no_difference 'User.count' do
444 assert_no_difference 'User.count' do
445 assert_equal false, User.anonymous.destroy
445 assert_equal false, User.anonymous.destroy
446 end
446 end
447 end
447 end
448
448
449 def test_password_change_should_destroy_tokens
449 def test_password_change_should_destroy_tokens
450 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
450 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
451 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
451 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
452
452
453 user = User.find(2)
453 user = User.find(2)
454 user.password, user.password_confirmation = "a new password", "a new password"
454 user.password, user.password_confirmation = "a new password", "a new password"
455 assert user.save
455 assert user.save
456
456
457 assert_nil Token.find_by_id(recovery_token.id)
457 assert_nil Token.find_by_id(recovery_token.id)
458 assert_nil Token.find_by_id(autologin_token.id)
458 assert_nil Token.find_by_id(autologin_token.id)
459 end
459 end
460
460
461 def test_mail_change_should_destroy_tokens
461 def test_mail_change_should_destroy_tokens
462 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
462 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
463 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
463 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
464
464
465 user = User.find(2)
465 user = User.find(2)
466 user.mail = "user@somwehere.com"
466 user.mail = "user@somwehere.com"
467 assert user.save
467 assert user.save
468
468
469 assert_nil Token.find_by_id(recovery_token.id)
469 assert_nil Token.find_by_id(recovery_token.id)
470 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
470 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
471 end
471 end
472
472
473 def test_change_on_other_fields_should_not_destroy_tokens
473 def test_change_on_other_fields_should_not_destroy_tokens
474 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
474 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
475 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
475 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
476
476
477 user = User.find(2)
477 user = User.find(2)
478 user.firstname = "Bobby"
478 user.firstname = "Bobby"
479 assert user.save
479 assert user.save
480
480
481 assert_equal recovery_token, Token.find_by_id(recovery_token.id)
481 assert_equal recovery_token, Token.find_by_id(recovery_token.id)
482 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
482 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
483 end
483 end
484
484
485 def test_validate_login_presence
485 def test_validate_login_presence
486 @admin.login = ""
486 @admin.login = ""
487 assert !@admin.save
487 assert !@admin.save
488 assert_equal 1, @admin.errors.count
488 assert_equal 1, @admin.errors.count
489 end
489 end
490
490
491 def test_validate_mail_notification_inclusion
491 def test_validate_mail_notification_inclusion
492 u = User.new
492 u = User.new
493 u.mail_notification = 'foo'
493 u.mail_notification = 'foo'
494 u.save
494 u.save
495 assert_not_equal [], u.errors[:mail_notification]
495 assert_not_equal [], u.errors[:mail_notification]
496 end
496 end
497
497
498 def test_password
498 def test_password
499 user = User.try_to_login("admin", "admin")
499 user = User.try_to_login("admin", "admin")
500 assert_kind_of User, user
500 assert_kind_of User, user
501 assert_equal "admin", user.login
501 assert_equal "admin", user.login
502 user.password = "hello123"
502 user.password = "hello123"
503 assert user.save
503 assert user.save
504
504
505 user = User.try_to_login("admin", "hello123")
505 user = User.try_to_login("admin", "hello123")
506 assert_kind_of User, user
506 assert_kind_of User, user
507 assert_equal "admin", user.login
507 assert_equal "admin", user.login
508 end
508 end
509
509
510 def test_validate_password_length
510 def test_validate_password_length
511 with_settings :password_min_length => '100' do
511 with_settings :password_min_length => '100' do
512 user = User.new(:firstname => "new100",
512 user = User.new(:firstname => "new100",
513 :lastname => "user100", :mail => "newuser100@somenet.foo")
513 :lastname => "user100", :mail => "newuser100@somenet.foo")
514 user.login = "newuser100"
514 user.login = "newuser100"
515 user.password, user.password_confirmation = "password100", "password100"
515 user.password, user.password_confirmation = "password100", "password100"
516 assert !user.save
516 assert !user.save
517 assert_equal 1, user.errors.count
517 assert_equal 1, user.errors.count
518 end
518 end
519 end
519 end
520
520
521 def test_name_format
521 def test_name_format
522 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
522 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
523 assert_equal 'Smith, John', @jsmith.name(:lastname_comma_firstname)
523 assert_equal 'Smith, John', @jsmith.name(:lastname_comma_firstname)
524 assert_equal 'J. Smith', @jsmith.name(:firstinitial_lastname)
524 assert_equal 'J. Smith', @jsmith.name(:firstinitial_lastname)
525 assert_equal 'J.-P. Lang', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').name(:firstinitial_lastname)
525 assert_equal 'J.-P. Lang', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').name(:firstinitial_lastname)
526 end
526 end
527
527
528 def test_name_should_use_setting_as_default_format
528 def test_name_should_use_setting_as_default_format
529 with_settings :user_format => :firstname_lastname do
529 with_settings :user_format => :firstname_lastname do
530 assert_equal 'John Smith', @jsmith.reload.name
530 assert_equal 'John Smith', @jsmith.reload.name
531 end
531 end
532 with_settings :user_format => :username do
532 with_settings :user_format => :username do
533 assert_equal 'jsmith', @jsmith.reload.name
533 assert_equal 'jsmith', @jsmith.reload.name
534 end
534 end
535 with_settings :user_format => :lastname do
535 with_settings :user_format => :lastname do
536 assert_equal 'Smith', @jsmith.reload.name
536 assert_equal 'Smith', @jsmith.reload.name
537 end
537 end
538 end
538 end
539
539
540 def test_today_should_return_the_day_according_to_user_time_zone
540 def test_today_should_return_the_day_according_to_user_time_zone
541 preference = User.find(1).pref
541 preference = User.find(1).pref
542 date = Date.new(2012, 05, 15)
542 date = Date.new(2012, 05, 15)
543 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
543 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
544 Date.stubs(:today).returns(date)
544 Date.stubs(:today).returns(date)
545 Time.stubs(:now).returns(time)
545 Time.stubs(:now).returns(time)
546
546
547 preference.update_attribute :time_zone, 'Baku' # UTC+4
547 preference.update_attribute :time_zone, 'Baku' # UTC+4
548 assert_equal '2012-05-16', User.find(1).today.to_s
548 assert_equal '2012-05-16', User.find(1).today.to_s
549
549
550 preference.update_attribute :time_zone, 'La Paz' # UTC-4
550 preference.update_attribute :time_zone, 'La Paz' # UTC-4
551 assert_equal '2012-05-15', User.find(1).today.to_s
551 assert_equal '2012-05-15', User.find(1).today.to_s
552
552
553 preference.update_attribute :time_zone, ''
553 preference.update_attribute :time_zone, ''
554 assert_equal '2012-05-15', User.find(1).today.to_s
554 assert_equal '2012-05-15', User.find(1).today.to_s
555 end
555 end
556
556
557 def test_time_to_date_should_return_the_date_according_to_user_time_zone
557 def test_time_to_date_should_return_the_date_according_to_user_time_zone
558 preference = User.find(1).pref
558 preference = User.find(1).pref
559 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
559 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
560
560
561 preference.update_attribute :time_zone, 'Baku' # UTC+4
561 preference.update_attribute :time_zone, 'Baku' # UTC+4
562 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
562 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
563
563
564 preference.update_attribute :time_zone, 'La Paz' # UTC-4
564 preference.update_attribute :time_zone, 'La Paz' # UTC-4
565 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
565 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
566
566
567 preference.update_attribute :time_zone, ''
567 preference.update_attribute :time_zone, ''
568 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
568 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
569 end
569 end
570
570
571 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
571 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
572 with_settings :user_format => 'lastname_comma_firstname' do
572 with_settings :user_format => 'lastname_comma_firstname' do
573 assert_equal ['users.lastname', 'users.firstname', 'users.id'],
573 assert_equal ['users.lastname', 'users.firstname', 'users.id'],
574 User.fields_for_order_statement
574 User.fields_for_order_statement
575 end
575 end
576 end
576 end
577
577
578 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
578 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
579 with_settings :user_format => 'lastname_firstname' do
579 with_settings :user_format => 'lastname_firstname' do
580 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'],
580 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'],
581 User.fields_for_order_statement('authors')
581 User.fields_for_order_statement('authors')
582 end
582 end
583 end
583 end
584
584
585 def test_fields_for_order_statement_with_blank_format_should_return_default
585 def test_fields_for_order_statement_with_blank_format_should_return_default
586 with_settings :user_format => '' do
586 with_settings :user_format => '' do
587 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
587 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
588 User.fields_for_order_statement
588 User.fields_for_order_statement
589 end
589 end
590 end
590 end
591
591
592 def test_fields_for_order_statement_with_invalid_format_should_return_default
592 def test_fields_for_order_statement_with_invalid_format_should_return_default
593 with_settings :user_format => 'foo' do
593 with_settings :user_format => 'foo' do
594 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
594 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
595 User.fields_for_order_statement
595 User.fields_for_order_statement
596 end
596 end
597 end
597 end
598
598
599 test ".try_to_login with good credentials should return the user" do
599 test ".try_to_login with good credentials should return the user" do
600 user = User.try_to_login("admin", "admin")
600 user = User.try_to_login("admin", "admin")
601 assert_kind_of User, user
601 assert_kind_of User, user
602 assert_equal "admin", user.login
602 assert_equal "admin", user.login
603 end
603 end
604
604
605 test ".try_to_login with wrong credentials should return nil" do
605 test ".try_to_login with wrong credentials should return nil" do
606 assert_nil User.try_to_login("admin", "foo")
606 assert_nil User.try_to_login("admin", "foo")
607 end
607 end
608
608
609 def test_try_to_login_with_locked_user_should_return_nil
609 def test_try_to_login_with_locked_user_should_return_nil
610 @jsmith.status = User::STATUS_LOCKED
610 @jsmith.status = User::STATUS_LOCKED
611 @jsmith.save!
611 @jsmith.save!
612
612
613 user = User.try_to_login("jsmith", "jsmith")
613 user = User.try_to_login("jsmith", "jsmith")
614 assert_equal nil, user
614 assert_equal nil, user
615 end
615 end
616
616
617 def test_try_to_login_with_locked_user_and_not_active_only_should_return_user
617 def test_try_to_login_with_locked_user_and_not_active_only_should_return_user
618 @jsmith.status = User::STATUS_LOCKED
618 @jsmith.status = User::STATUS_LOCKED
619 @jsmith.save!
619 @jsmith.save!
620
620
621 user = User.try_to_login("jsmith", "jsmith", false)
621 user = User.try_to_login("jsmith", "jsmith", false)
622 assert_equal @jsmith, user
622 assert_equal @jsmith, user
623 end
623 end
624
624
625 test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do
625 test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do
626 user = User.try_to_login("AdMin", "admin")
626 user = User.try_to_login("AdMin", "admin")
627 assert_kind_of User, user
627 assert_kind_of User, user
628 assert_equal "admin", user.login
628 assert_equal "admin", user.login
629 end
629 end
630
630
631 test ".try_to_login should select the exact matching user first" do
631 test ".try_to_login should select the exact matching user first" do
632 case_sensitive_user = User.generate! do |user|
632 case_sensitive_user = User.generate! do |user|
633 user.password = "admin123"
633 user.password = "admin123"
634 end
634 end
635 # bypass validations to make it appear like existing data
635 # bypass validations to make it appear like existing data
636 case_sensitive_user.update_attribute(:login, 'ADMIN')
636 case_sensitive_user.update_attribute(:login, 'ADMIN')
637
637
638 user = User.try_to_login("ADMIN", "admin123")
638 user = User.try_to_login("ADMIN", "admin123")
639 assert_kind_of User, user
639 assert_kind_of User, user
640 assert_equal "ADMIN", user.login
640 assert_equal "ADMIN", user.login
641 end
641 end
642
642
643 if ldap_configured?
643 if ldap_configured?
644 test "#try_to_login using LDAP with failed connection to the LDAP server" do
644 test "#try_to_login using LDAP with failed connection to the LDAP server" do
645 auth_source = AuthSourceLdap.find(1)
645 auth_source = AuthSourceLdap.find(1)
646 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
646 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
647
647
648 assert_equal nil, User.try_to_login('edavis', 'wrong')
648 assert_equal nil, User.try_to_login('edavis', 'wrong')
649 end
649 end
650
650
651 test "#try_to_login using LDAP" do
651 test "#try_to_login using LDAP" do
652 assert_equal nil, User.try_to_login('edavis', 'wrong')
652 assert_equal nil, User.try_to_login('edavis', 'wrong')
653 end
653 end
654
654
655 test "#try_to_login using LDAP binding with user's account" do
655 test "#try_to_login using LDAP binding with user's account" do
656 auth_source = AuthSourceLdap.find(1)
656 auth_source = AuthSourceLdap.find(1)
657 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
657 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
658 auth_source.account_password = ''
658 auth_source.account_password = ''
659 auth_source.save!
659 auth_source.save!
660
660
661 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
661 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
662 ldap_user.login = 'example1'
662 ldap_user.login = 'example1'
663 ldap_user.save!
663 ldap_user.save!
664
664
665 assert_equal ldap_user, User.try_to_login('example1', '123456')
665 assert_equal ldap_user, User.try_to_login('example1', '123456')
666 assert_nil User.try_to_login('example1', '11111')
666 assert_nil User.try_to_login('example1', '11111')
667 end
667 end
668
668
669 test "#try_to_login using LDAP on the fly registration" do
669 test "#try_to_login using LDAP on the fly registration" do
670 AuthSourceLdap.find(1).update_attribute :onthefly_register, true
670 AuthSourceLdap.find(1).update_attribute :onthefly_register, true
671
671
672 assert_difference('User.count') do
672 assert_difference('User.count') do
673 assert User.try_to_login('edavis', '123456')
673 assert User.try_to_login('edavis', '123456')
674 end
674 end
675
675
676 assert_no_difference('User.count') do
676 assert_no_difference('User.count') do
677 assert User.try_to_login('edavis', '123456')
677 assert User.try_to_login('edavis', '123456')
678 end
678 end
679
679
680 assert_nil User.try_to_login('example1', '11111')
680 assert_nil User.try_to_login('example1', '11111')
681 end
681 end
682
682
683 test "#try_to_login using LDAP on the fly registration and binding with user's account" do
683 test "#try_to_login using LDAP on the fly registration and binding with user's account" do
684 auth_source = AuthSourceLdap.find(1)
684 auth_source = AuthSourceLdap.find(1)
685 auth_source.update_attribute :onthefly_register, true
685 auth_source.update_attribute :onthefly_register, true
686 auth_source = AuthSourceLdap.find(1)
686 auth_source = AuthSourceLdap.find(1)
687 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
687 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
688 auth_source.account_password = ''
688 auth_source.account_password = ''
689 auth_source.save!
689 auth_source.save!
690
690
691 assert_difference('User.count') do
691 assert_difference('User.count') do
692 assert User.try_to_login('example1', '123456')
692 assert User.try_to_login('example1', '123456')
693 end
693 end
694
694
695 assert_no_difference('User.count') do
695 assert_no_difference('User.count') do
696 assert User.try_to_login('example1', '123456')
696 assert User.try_to_login('example1', '123456')
697 end
697 end
698
698
699 assert_nil User.try_to_login('example1', '11111')
699 assert_nil User.try_to_login('example1', '11111')
700 end
700 end
701
701
702 else
702 else
703 puts "Skipping LDAP tests."
703 puts "Skipping LDAP tests."
704 end
704 end
705
705
706 def test_create_anonymous
706 def test_create_anonymous
707 AnonymousUser.delete_all
707 AnonymousUser.delete_all
708 anon = User.anonymous
708 anon = User.anonymous
709 assert !anon.new_record?
709 assert !anon.new_record?
710 assert_kind_of AnonymousUser, anon
710 assert_kind_of AnonymousUser, anon
711 end
711 end
712
712
713 def test_ensure_single_anonymous_user
713 def test_ensure_single_anonymous_user
714 AnonymousUser.delete_all
714 AnonymousUser.delete_all
715 anon1 = User.anonymous
715 anon1 = User.anonymous
716 assert !anon1.new_record?
716 assert !anon1.new_record?
717 assert_kind_of AnonymousUser, anon1
717 assert_kind_of AnonymousUser, anon1
718 anon2 = AnonymousUser.create(
718 anon2 = AnonymousUser.create(
719 :lastname => 'Anonymous', :firstname => '',
719 :lastname => 'Anonymous', :firstname => '',
720 :login => '', :status => 0)
720 :login => '', :status => 0)
721 assert_equal 1, anon2.errors.count
721 assert_equal 1, anon2.errors.count
722 end
722 end
723
723
724 def test_rss_key
724 def test_rss_key
725 assert_nil @jsmith.rss_token
725 assert_nil @jsmith.rss_token
726 key = @jsmith.rss_key
726 key = @jsmith.rss_key
727 assert_equal 40, key.length
727 assert_equal 40, key.length
728
728
729 @jsmith.reload
729 @jsmith.reload
730 assert_equal key, @jsmith.rss_key
730 assert_equal key, @jsmith.rss_key
731 end
731 end
732
732
733 def test_rss_key_should_not_be_generated_twice
733 def test_rss_key_should_not_be_generated_twice
734 assert_difference 'Token.count', 1 do
734 assert_difference 'Token.count', 1 do
735 key1 = @jsmith.rss_key
735 key1 = @jsmith.rss_key
736 key2 = @jsmith.rss_key
736 key2 = @jsmith.rss_key
737 assert_equal key1, key2
737 assert_equal key1, key2
738 end
738 end
739 end
739 end
740
740
741 def test_api_key_should_not_be_generated_twice
741 def test_api_key_should_not_be_generated_twice
742 assert_difference 'Token.count', 1 do
742 assert_difference 'Token.count', 1 do
743 key1 = @jsmith.api_key
743 key1 = @jsmith.api_key
744 key2 = @jsmith.api_key
744 key2 = @jsmith.api_key
745 assert_equal key1, key2
745 assert_equal key1, key2
746 end
746 end
747 end
747 end
748
748
749 test "#api_key should generate a new one if the user doesn't have one" do
749 test "#api_key should generate a new one if the user doesn't have one" do
750 user = User.generate!(:api_token => nil)
750 user = User.generate!(:api_token => nil)
751 assert_nil user.api_token
751 assert_nil user.api_token
752
752
753 key = user.api_key
753 key = user.api_key
754 assert_equal 40, key.length
754 assert_equal 40, key.length
755 user.reload
755 user.reload
756 assert_equal key, user.api_key
756 assert_equal key, user.api_key
757 end
757 end
758
758
759 test "#api_key should return the existing api token value" do
759 test "#api_key should return the existing api token value" do
760 user = User.generate!
760 user = User.generate!
761 token = Token.create!(:action => 'api')
761 token = Token.create!(:action => 'api')
762 user.api_token = token
762 user.api_token = token
763 assert user.save
763 assert user.save
764
764
765 assert_equal token.value, user.api_key
765 assert_equal token.value, user.api_key
766 end
766 end
767
767
768 test "#find_by_api_key should return nil if no matching key is found" do
768 test "#find_by_api_key should return nil if no matching key is found" do
769 assert_nil User.find_by_api_key('zzzzzzzzz')
769 assert_nil User.find_by_api_key('zzzzzzzzz')
770 end
770 end
771
771
772 test "#find_by_api_key should return nil if the key is found for an inactive user" do
772 test "#find_by_api_key should return nil if the key is found for an inactive user" do
773 user = User.generate!
773 user = User.generate!
774 user.status = User::STATUS_LOCKED
774 user.status = User::STATUS_LOCKED
775 token = Token.create!(:action => 'api')
775 token = Token.create!(:action => 'api')
776 user.api_token = token
776 user.api_token = token
777 user.save
777 user.save
778
778
779 assert_nil User.find_by_api_key(token.value)
779 assert_nil User.find_by_api_key(token.value)
780 end
780 end
781
781
782 test "#find_by_api_key should return the user if the key is found for an active user" do
782 test "#find_by_api_key should return the user if the key is found for an active user" do
783 user = User.generate!
783 user = User.generate!
784 token = Token.create!(:action => 'api')
784 token = Token.create!(:action => 'api')
785 user.api_token = token
785 user.api_token = token
786 user.save
786 user.save
787
787
788 assert_equal user, User.find_by_api_key(token.value)
788 assert_equal user, User.find_by_api_key(token.value)
789 end
789 end
790
790
791 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
791 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
792 user = User.find_by_login("admin")
792 user = User.find_by_login("admin")
793 user.password = "admin"
793 user.password = "admin"
794 assert user.save(:validate => false)
794 assert user.save(:validate => false)
795
795
796 assert_equal false, User.default_admin_account_changed?
796 assert_equal false, User.default_admin_account_changed?
797 end
797 end
798
798
799 def test_default_admin_account_changed_should_return_true_if_password_was_changed
799 def test_default_admin_account_changed_should_return_true_if_password_was_changed
800 user = User.find_by_login("admin")
800 user = User.find_by_login("admin")
801 user.password = "newpassword"
801 user.password = "newpassword"
802 user.save!
802 user.save!
803
803
804 assert_equal true, User.default_admin_account_changed?
804 assert_equal true, User.default_admin_account_changed?
805 end
805 end
806
806
807 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
807 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
808 user = User.find_by_login("admin")
808 user = User.find_by_login("admin")
809 user.password = "admin"
809 user.password = "admin"
810 user.status = User::STATUS_LOCKED
810 user.status = User::STATUS_LOCKED
811 assert user.save(:validate => false)
811 assert user.save(:validate => false)
812
812
813 assert_equal true, User.default_admin_account_changed?
813 assert_equal true, User.default_admin_account_changed?
814 end
814 end
815
815
816 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
816 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
817 user = User.find_by_login("admin")
817 user = User.find_by_login("admin")
818 user.destroy
818 user.destroy
819
819
820 assert_equal true, User.default_admin_account_changed?
820 assert_equal true, User.default_admin_account_changed?
821 end
821 end
822
822
823 def test_membership_with_project_should_return_membership
823 def test_membership_with_project_should_return_membership
824 project = Project.find(1)
824 project = Project.find(1)
825
825
826 membership = @jsmith.membership(project)
826 membership = @jsmith.membership(project)
827 assert_kind_of Member, membership
827 assert_kind_of Member, membership
828 assert_equal @jsmith, membership.user
828 assert_equal @jsmith, membership.user
829 assert_equal project, membership.project
829 assert_equal project, membership.project
830 end
830 end
831
831
832 def test_membership_with_project_id_should_return_membership
832 def test_membership_with_project_id_should_return_membership
833 project = Project.find(1)
833 project = Project.find(1)
834
834
835 membership = @jsmith.membership(1)
835 membership = @jsmith.membership(1)
836 assert_kind_of Member, membership
836 assert_kind_of Member, membership
837 assert_equal @jsmith, membership.user
837 assert_equal @jsmith, membership.user
838 assert_equal project, membership.project
838 assert_equal project, membership.project
839 end
839 end
840
840
841 def test_membership_for_non_member_should_return_nil
841 def test_membership_for_non_member_should_return_nil
842 project = Project.find(1)
842 project = Project.find(1)
843
843
844 user = User.generate!
844 user = User.generate!
845 membership = user.membership(1)
845 membership = user.membership(1)
846 assert_nil membership
846 assert_nil membership
847 end
847 end
848
848
849 def test_roles_for_project_with_member_on_public_project_should_return_roles_and_non_member
849 def test_roles_for_project_with_member_on_public_project_should_return_roles_and_non_member
850 roles = @jsmith.roles_for_project(Project.find(1))
850 roles = @jsmith.roles_for_project(Project.find(1))
851 assert_kind_of Role, roles.first
851 assert_kind_of Role, roles.first
852 assert_equal ["Manager"], roles.map(&:name)
852 assert_equal ["Manager"], roles.map(&:name)
853 end
853 end
854
854
855 def test_roles_for_project_with_member_on_private_project_should_return_roles
855 def test_roles_for_project_with_member_on_private_project_should_return_roles
856 Project.find(1).update_attribute :is_public, false
856 Project.find(1).update_attribute :is_public, false
857
857
858 roles = @jsmith.roles_for_project(Project.find(1))
858 roles = @jsmith.roles_for_project(Project.find(1))
859 assert_kind_of Role, roles.first
859 assert_kind_of Role, roles.first
860 assert_equal ["Manager"], roles.map(&:name)
860 assert_equal ["Manager"], roles.map(&:name)
861 end
861 end
862
862
863 def test_roles_for_project_with_non_member_with_public_project_should_return_non_member
863 def test_roles_for_project_with_non_member_with_public_project_should_return_non_member
864 set_language_if_valid 'en'
864 set_language_if_valid 'en'
865 roles = User.find(8).roles_for_project(Project.find(1))
865 roles = User.find(8).roles_for_project(Project.find(1))
866 assert_equal ["Non member"], roles.map(&:name)
866 assert_equal ["Non member"], roles.map(&:name)
867 end
867 end
868
868
869 def test_roles_for_project_with_non_member_with_public_project_and_override_should_return_override_roles
869 def test_roles_for_project_with_non_member_with_public_project_and_override_should_return_override_roles
870 project = Project.find(1)
870 project = Project.find(1)
871 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
871 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
872 roles = User.find(8).roles_for_project(project)
872 roles = User.find(8).roles_for_project(project)
873 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
873 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
874 end
874 end
875
875
876 def test_roles_for_project_with_non_member_with_private_project_should_return_no_roles
876 def test_roles_for_project_with_non_member_with_private_project_should_return_no_roles
877 Project.find(1).update_attribute :is_public, false
877 Project.find(1).update_attribute :is_public, false
878
878
879 roles = User.find(8).roles_for_project(Project.find(1))
879 roles = User.find(8).roles_for_project(Project.find(1))
880 assert_equal [], roles.map(&:name)
880 assert_equal [], roles.map(&:name)
881 end
881 end
882
882
883 def test_roles_for_project_with_non_member_with_private_project_and_override_should_return_no_roles
883 def test_roles_for_project_with_non_member_with_private_project_and_override_should_return_no_roles
884 project = Project.find(1)
884 project = Project.find(1)
885 project.update_attribute :is_public, false
885 project.update_attribute :is_public, false
886 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
886 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
887 roles = User.find(8).roles_for_project(project)
887 roles = User.find(8).roles_for_project(project)
888 assert_equal [], roles.map(&:name).sort
888 assert_equal [], roles.map(&:name).sort
889 end
889 end
890
890
891 def test_roles_for_project_with_anonymous_with_public_project_should_return_anonymous
891 def test_roles_for_project_with_anonymous_with_public_project_should_return_anonymous
892 set_language_if_valid 'en'
892 set_language_if_valid 'en'
893 roles = User.anonymous.roles_for_project(Project.find(1))
893 roles = User.anonymous.roles_for_project(Project.find(1))
894 assert_equal ["Anonymous"], roles.map(&:name)
894 assert_equal ["Anonymous"], roles.map(&:name)
895 end
895 end
896
896
897 def test_roles_for_project_with_anonymous_with_public_project_and_override_should_return_override_roles
897 def test_roles_for_project_with_anonymous_with_public_project_and_override_should_return_override_roles
898 project = Project.find(1)
898 project = Project.find(1)
899 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
899 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
900 roles = User.anonymous.roles_for_project(project)
900 roles = User.anonymous.roles_for_project(project)
901 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
901 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
902 end
902 end
903
903
904 def test_roles_for_project_with_anonymous_with_private_project_should_return_no_roles
904 def test_roles_for_project_with_anonymous_with_private_project_should_return_no_roles
905 Project.find(1).update_attribute :is_public, false
905 Project.find(1).update_attribute :is_public, false
906
906
907 roles = User.anonymous.roles_for_project(Project.find(1))
907 roles = User.anonymous.roles_for_project(Project.find(1))
908 assert_equal [], roles.map(&:name)
908 assert_equal [], roles.map(&:name)
909 end
909 end
910
910
911 def test_roles_for_project_with_anonymous_with_private_project_and_override_should_return_no_roles
911 def test_roles_for_project_with_anonymous_with_private_project_and_override_should_return_no_roles
912 project = Project.find(1)
912 project = Project.find(1)
913 project.update_attribute :is_public, false
913 project.update_attribute :is_public, false
914 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
914 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
915 roles = User.anonymous.roles_for_project(project)
915 roles = User.anonymous.roles_for_project(project)
916 assert_equal [], roles.map(&:name).sort
916 assert_equal [], roles.map(&:name).sort
917 end
917 end
918
918
919 def test_roles_for_project_should_be_unique
919 def test_roles_for_project_should_be_unique
920 m = Member.new(:user_id => 1, :project_id => 1)
920 m = Member.new(:user_id => 1, :project_id => 1)
921 m.member_roles.build(:role_id => 1)
921 m.member_roles.build(:role_id => 1)
922 m.member_roles.build(:role_id => 1)
922 m.member_roles.build(:role_id => 1)
923 m.save!
923 m.save!
924
924
925 user = User.find(1)
925 user = User.find(1)
926 project = Project.find(1)
926 project = Project.find(1)
927 assert_equal 1, user.roles_for_project(project).size
927 assert_equal 1, user.roles_for_project(project).size
928 assert_equal [1], user.roles_for_project(project).map(&:id)
928 assert_equal [1], user.roles_for_project(project).map(&:id)
929 end
929 end
930
930
931 def test_projects_by_role_for_user_with_role
931 def test_projects_by_role_for_user_with_role
932 user = User.find(2)
932 user = User.find(2)
933 assert_kind_of Hash, user.projects_by_role
933 assert_kind_of Hash, user.projects_by_role
934 assert_equal 2, user.projects_by_role.size
934 assert_equal 2, user.projects_by_role.size
935 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
935 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
936 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
936 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
937 end
937 end
938
938
939 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
939 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
940 user = User.find(2)
940 user = User.find(2)
941 assert_equal [], user.projects_by_role[Role.find(3)]
941 assert_equal [], user.projects_by_role[Role.find(3)]
942 # should not update the hash
942 # should not update the hash
943 assert_nil user.projects_by_role.values.detect(&:blank?)
943 assert_nil user.projects_by_role.values.detect(&:blank?)
944 end
944 end
945
945
946 def test_projects_by_role_for_user_with_no_role
946 def test_projects_by_role_for_user_with_no_role
947 user = User.generate!
947 user = User.generate!
948 assert_equal({}, user.projects_by_role)
948 assert_equal({}, user.projects_by_role)
949 end
949 end
950
950
951 def test_projects_by_role_for_anonymous
951 def test_projects_by_role_for_anonymous
952 assert_equal({}, User.anonymous.projects_by_role)
952 assert_equal({}, User.anonymous.projects_by_role)
953 end
953 end
954
954
955 def test_valid_notification_options
955 def test_valid_notification_options
956 # without memberships
956 # without memberships
957 assert_equal 5, User.find(7).valid_notification_options.size
957 assert_equal 5, User.find(7).valid_notification_options.size
958 # with memberships
958 # with memberships
959 assert_equal 6, User.find(2).valid_notification_options.size
959 assert_equal 6, User.find(2).valid_notification_options.size
960 end
960 end
961
961
962 def test_valid_notification_options_class_method
962 def test_valid_notification_options_class_method
963 assert_equal 5, User.valid_notification_options.size
963 assert_equal 5, User.valid_notification_options.size
964 assert_equal 5, User.valid_notification_options(User.find(7)).size
964 assert_equal 5, User.valid_notification_options(User.find(7)).size
965 assert_equal 6, User.valid_notification_options(User.find(2)).size
965 assert_equal 6, User.valid_notification_options(User.find(2)).size
966 end
966 end
967
967
968 def test_notified_project_ids_setter_should_coerce_to_unique_integer_array
968 def test_notified_project_ids_setter_should_coerce_to_unique_integer_array
969 @jsmith.notified_project_ids = ["1", "123", "2u", "wrong", "12", 6, 12, -35, ""]
969 @jsmith.notified_project_ids = ["1", "123", "2u", "wrong", "12", 6, 12, -35, ""]
970 assert_equal [1, 123, 2, 12, 6], @jsmith.notified_projects_ids
970 assert_equal [1, 123, 2, 12, 6], @jsmith.notified_projects_ids
971 end
971 end
972
972
973 def test_mail_notification_all
973 def test_mail_notification_all
974 @jsmith.mail_notification = 'all'
974 @jsmith.mail_notification = 'all'
975 @jsmith.notified_project_ids = []
975 @jsmith.notified_project_ids = []
976 @jsmith.save
976 @jsmith.save
977 @jsmith.reload
977 @jsmith.reload
978 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
978 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
979 end
979 end
980
980
981 def test_mail_notification_selected
981 def test_mail_notification_selected
982 @jsmith.mail_notification = 'selected'
982 @jsmith.mail_notification = 'selected'
983 @jsmith.notified_project_ids = [1]
983 @jsmith.notified_project_ids = [1]
984 @jsmith.save
984 @jsmith.save
985 @jsmith.reload
985 @jsmith.reload
986 assert Project.find(1).recipients.include?(@jsmith.mail)
986 assert Project.find(1).recipients.include?(@jsmith.mail)
987 end
987 end
988
988
989 def test_mail_notification_only_my_events
989 def test_mail_notification_only_my_events
990 @jsmith.mail_notification = 'only_my_events'
990 @jsmith.mail_notification = 'only_my_events'
991 @jsmith.notified_project_ids = []
991 @jsmith.notified_project_ids = []
992 @jsmith.save
992 @jsmith.save
993 @jsmith.reload
993 @jsmith.reload
994 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
994 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
995 end
995 end
996
996
997 def test_comments_sorting_preference
997 def test_comments_sorting_preference
998 assert !@jsmith.wants_comments_in_reverse_order?
998 assert !@jsmith.wants_comments_in_reverse_order?
999 @jsmith.pref.comments_sorting = 'asc'
999 @jsmith.pref.comments_sorting = 'asc'
1000 assert !@jsmith.wants_comments_in_reverse_order?
1000 assert !@jsmith.wants_comments_in_reverse_order?
1001 @jsmith.pref.comments_sorting = 'desc'
1001 @jsmith.pref.comments_sorting = 'desc'
1002 assert @jsmith.wants_comments_in_reverse_order?
1002 assert @jsmith.wants_comments_in_reverse_order?
1003 end
1003 end
1004
1004
1005 def test_find_by_mail_should_be_case_insensitive
1005 def test_find_by_mail_should_be_case_insensitive
1006 u = User.find_by_mail('JSmith@somenet.foo')
1006 u = User.find_by_mail('JSmith@somenet.foo')
1007 assert_not_nil u
1007 assert_not_nil u
1008 assert_equal 'jsmith@somenet.foo', u.mail
1008 assert_equal 'jsmith@somenet.foo', u.mail
1009 end
1009 end
1010
1010
1011 def test_random_password
1011 def test_random_password
1012 u = User.new
1012 u = User.new
1013 u.random_password
1013 u.random_password
1014 assert !u.password.blank?
1014 assert !u.password.blank?
1015 assert !u.password_confirmation.blank?
1015 assert !u.password_confirmation.blank?
1016 end
1016 end
1017
1017
1018 test "#change_password_allowed? should be allowed if no auth source is set" do
1018 test "#change_password_allowed? should be allowed if no auth source is set" do
1019 user = User.generate!
1019 user = User.generate!
1020 assert user.change_password_allowed?
1020 assert user.change_password_allowed?
1021 end
1021 end
1022
1022
1023 test "#change_password_allowed? should delegate to the auth source" do
1023 test "#change_password_allowed? should delegate to the auth source" do
1024 user = User.generate!
1024 user = User.generate!
1025
1025
1026 allowed_auth_source = AuthSource.generate!
1026 allowed_auth_source = AuthSource.generate!
1027 def allowed_auth_source.allow_password_changes?; true; end
1027 def allowed_auth_source.allow_password_changes?; true; end
1028
1028
1029 denied_auth_source = AuthSource.generate!
1029 denied_auth_source = AuthSource.generate!
1030 def denied_auth_source.allow_password_changes?; false; end
1030 def denied_auth_source.allow_password_changes?; false; end
1031
1031
1032 assert user.change_password_allowed?
1032 assert user.change_password_allowed?
1033
1033
1034 user.auth_source = allowed_auth_source
1034 user.auth_source = allowed_auth_source
1035 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
1035 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
1036
1036
1037 user.auth_source = denied_auth_source
1037 user.auth_source = denied_auth_source
1038 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
1038 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
1039 end
1039 end
1040
1040
1041 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
1041 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
1042 with_settings :unsubscribe => '1' do
1042 with_settings :unsubscribe => '1' do
1043 assert_equal true, User.find(2).own_account_deletable?
1043 assert_equal true, User.find(2).own_account_deletable?
1044 end
1044 end
1045 end
1045 end
1046
1046
1047 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
1047 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
1048 with_settings :unsubscribe => '0' do
1048 with_settings :unsubscribe => '0' do
1049 assert_equal false, User.find(2).own_account_deletable?
1049 assert_equal false, User.find(2).own_account_deletable?
1050 end
1050 end
1051 end
1051 end
1052
1052
1053 def test_own_account_deletable_should_be_false_for_a_single_admin
1053 def test_own_account_deletable_should_be_false_for_a_single_admin
1054 User.delete_all(["admin = ? AND id <> ?", true, 1])
1054 User.where(["admin = ? AND id <> ?", true, 1]).delete_all
1055
1055
1056 with_settings :unsubscribe => '1' do
1056 with_settings :unsubscribe => '1' do
1057 assert_equal false, User.find(1).own_account_deletable?
1057 assert_equal false, User.find(1).own_account_deletable?
1058 end
1058 end
1059 end
1059 end
1060
1060
1061 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
1061 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
1062 User.generate! do |user|
1062 User.generate! do |user|
1063 user.admin = true
1063 user.admin = true
1064 end
1064 end
1065
1065
1066 with_settings :unsubscribe => '1' do
1066 with_settings :unsubscribe => '1' do
1067 assert_equal true, User.find(1).own_account_deletable?
1067 assert_equal true, User.find(1).own_account_deletable?
1068 end
1068 end
1069 end
1069 end
1070
1070
1071 test "#allowed_to? for archived project should return false" do
1071 test "#allowed_to? for archived project should return false" do
1072 project = Project.find(1)
1072 project = Project.find(1)
1073 project.archive
1073 project.archive
1074 project.reload
1074 project.reload
1075 assert_equal false, @admin.allowed_to?(:view_issues, project)
1075 assert_equal false, @admin.allowed_to?(:view_issues, project)
1076 end
1076 end
1077
1077
1078 test "#allowed_to? for closed project should return true for read actions" do
1078 test "#allowed_to? for closed project should return true for read actions" do
1079 project = Project.find(1)
1079 project = Project.find(1)
1080 project.close
1080 project.close
1081 project.reload
1081 project.reload
1082 assert_equal false, @admin.allowed_to?(:edit_project, project)
1082 assert_equal false, @admin.allowed_to?(:edit_project, project)
1083 assert_equal true, @admin.allowed_to?(:view_project, project)
1083 assert_equal true, @admin.allowed_to?(:view_project, project)
1084 end
1084 end
1085
1085
1086 test "#allowed_to? for project with module disabled should return false" do
1086 test "#allowed_to? for project with module disabled should return false" do
1087 project = Project.find(1)
1087 project = Project.find(1)
1088 project.enabled_module_names = ["issue_tracking"]
1088 project.enabled_module_names = ["issue_tracking"]
1089 assert_equal true, @admin.allowed_to?(:add_issues, project)
1089 assert_equal true, @admin.allowed_to?(:add_issues, project)
1090 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
1090 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
1091 end
1091 end
1092
1092
1093 test "#allowed_to? for admin users should return true" do
1093 test "#allowed_to? for admin users should return true" do
1094 project = Project.find(1)
1094 project = Project.find(1)
1095 assert ! @admin.member_of?(project)
1095 assert ! @admin.member_of?(project)
1096 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
1096 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
1097 assert_equal true, @admin.allowed_to?(p.to_sym, project)
1097 assert_equal true, @admin.allowed_to?(p.to_sym, project)
1098 end
1098 end
1099 end
1099 end
1100
1100
1101 test "#allowed_to? for normal users" do
1101 test "#allowed_to? for normal users" do
1102 project = Project.find(1)
1102 project = Project.find(1)
1103 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
1103 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
1104 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
1104 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
1105 end
1105 end
1106
1106
1107 test "#allowed_to? with empty array should return false" do
1107 test "#allowed_to? with empty array should return false" do
1108 assert_equal false, @admin.allowed_to?(:view_project, [])
1108 assert_equal false, @admin.allowed_to?(:view_project, [])
1109 end
1109 end
1110
1110
1111 test "#allowed_to? with multiple projects" do
1111 test "#allowed_to? with multiple projects" do
1112 assert_equal true, @admin.allowed_to?(:view_project, Project.all.to_a)
1112 assert_equal true, @admin.allowed_to?(:view_project, Project.all.to_a)
1113 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all.to_a) #cannot see Project(2)
1113 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all.to_a) #cannot see Project(2)
1114 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects.to_a) #Manager or Developer everywhere
1114 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects.to_a) #Manager or Developer everywhere
1115 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects.to_a) #Dev cannot delete_issue_watchers
1115 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects.to_a) #Dev cannot delete_issue_watchers
1116 end
1116 end
1117
1117
1118 test "#allowed_to? with with options[:global] should return true if user has one role with the permission" do
1118 test "#allowed_to? with with options[:global] should return true if user has one role with the permission" do
1119 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1119 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1120 @anonymous = User.find(6)
1120 @anonymous = User.find(6)
1121 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
1121 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
1122 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
1122 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
1123 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
1123 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
1124 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
1124 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
1125 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
1125 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
1126 end
1126 end
1127
1127
1128 # this is just a proxy method, the test only calls it to ensure it doesn't break trivially
1128 # this is just a proxy method, the test only calls it to ensure it doesn't break trivially
1129 test "#allowed_to_globally?" do
1129 test "#allowed_to_globally?" do
1130 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1130 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1131 @anonymous = User.find(6)
1131 @anonymous = User.find(6)
1132 assert_equal true, @jsmith.allowed_to_globally?(:delete_issue_watchers)
1132 assert_equal true, @jsmith.allowed_to_globally?(:delete_issue_watchers)
1133 assert_equal false, @dlopper2.allowed_to_globally?(:delete_issue_watchers)
1133 assert_equal false, @dlopper2.allowed_to_globally?(:delete_issue_watchers)
1134 assert_equal true, @dlopper2.allowed_to_globally?(:add_issues)
1134 assert_equal true, @dlopper2.allowed_to_globally?(:add_issues)
1135 assert_equal false, @anonymous.allowed_to_globally?(:add_issues)
1135 assert_equal false, @anonymous.allowed_to_globally?(:add_issues)
1136 assert_equal true, @anonymous.allowed_to_globally?(:view_issues)
1136 assert_equal true, @anonymous.allowed_to_globally?(:view_issues)
1137 end
1137 end
1138
1138
1139 def test_notify_about_issue
1139 def test_notify_about_issue
1140 project = Project.find(1)
1140 project = Project.find(1)
1141 author = User.generate!
1141 author = User.generate!
1142 assignee = User.generate!
1142 assignee = User.generate!
1143 member = User.generate!
1143 member = User.generate!
1144 Member.create!(:user => member, :project => project, :role_ids => [1])
1144 Member.create!(:user => member, :project => project, :role_ids => [1])
1145 issue = Issue.generate!(:project => project, :assigned_to => assignee, :author => author)
1145 issue = Issue.generate!(:project => project, :assigned_to => assignee, :author => author)
1146
1146
1147 tests = {
1147 tests = {
1148 author => %w(all only_my_events only_owner selected),
1148 author => %w(all only_my_events only_owner selected),
1149 assignee => %w(all only_my_events only_assigned selected),
1149 assignee => %w(all only_my_events only_assigned selected),
1150 member => %w(all)
1150 member => %w(all)
1151 }
1151 }
1152
1152
1153 tests.each do |user, expected|
1153 tests.each do |user, expected|
1154 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1154 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1155 user.mail_notification = option
1155 user.mail_notification = option
1156 assert_equal expected.include?(option), user.notify_about?(issue)
1156 assert_equal expected.include?(option), user.notify_about?(issue)
1157 end
1157 end
1158 end
1158 end
1159 end
1159 end
1160
1160
1161 def test_notify_about_issue_for_previous_assignee
1161 def test_notify_about_issue_for_previous_assignee
1162 assignee = User.generate!(:mail_notification => 'only_assigned')
1162 assignee = User.generate!(:mail_notification => 'only_assigned')
1163 new_assignee = User.generate!(:mail_notification => 'only_assigned')
1163 new_assignee = User.generate!(:mail_notification => 'only_assigned')
1164 issue = Issue.generate!(:assigned_to => assignee)
1164 issue = Issue.generate!(:assigned_to => assignee)
1165
1165
1166 assert assignee.notify_about?(issue)
1166 assert assignee.notify_about?(issue)
1167 assert !new_assignee.notify_about?(issue)
1167 assert !new_assignee.notify_about?(issue)
1168
1168
1169 issue.assigned_to = new_assignee
1169 issue.assigned_to = new_assignee
1170 assert assignee.notify_about?(issue)
1170 assert assignee.notify_about?(issue)
1171 assert new_assignee.notify_about?(issue)
1171 assert new_assignee.notify_about?(issue)
1172
1172
1173 issue.save!
1173 issue.save!
1174 assert !assignee.notify_about?(issue)
1174 assert !assignee.notify_about?(issue)
1175 assert new_assignee.notify_about?(issue)
1175 assert new_assignee.notify_about?(issue)
1176 end
1176 end
1177
1177
1178 def test_notify_about_news
1178 def test_notify_about_news
1179 user = User.generate!
1179 user = User.generate!
1180 news = News.new
1180 news = News.new
1181
1181
1182 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1182 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1183 user.mail_notification = option
1183 user.mail_notification = option
1184 assert_equal (option != 'none'), user.notify_about?(news)
1184 assert_equal (option != 'none'), user.notify_about?(news)
1185 end
1185 end
1186 end
1186 end
1187
1187
1188 def test_salt_unsalted_passwords
1188 def test_salt_unsalted_passwords
1189 # Restore a user with an unsalted password
1189 # Restore a user with an unsalted password
1190 user = User.find(1)
1190 user = User.find(1)
1191 user.salt = nil
1191 user.salt = nil
1192 user.hashed_password = User.hash_password("unsalted")
1192 user.hashed_password = User.hash_password("unsalted")
1193 user.save!
1193 user.save!
1194
1194
1195 User.salt_unsalted_passwords!
1195 User.salt_unsalted_passwords!
1196
1196
1197 user.reload
1197 user.reload
1198 # Salt added
1198 # Salt added
1199 assert !user.salt.blank?
1199 assert !user.salt.blank?
1200 # Password still valid
1200 # Password still valid
1201 assert user.check_password?("unsalted")
1201 assert user.check_password?("unsalted")
1202 assert_equal user, User.try_to_login(user.login, "unsalted")
1202 assert_equal user, User.try_to_login(user.login, "unsalted")
1203 end
1203 end
1204
1204
1205 if Object.const_defined?(:OpenID)
1205 if Object.const_defined?(:OpenID)
1206 def test_setting_identity_url
1206 def test_setting_identity_url
1207 normalized_open_id_url = 'http://example.com/'
1207 normalized_open_id_url = 'http://example.com/'
1208 u = User.new( :identity_url => 'http://example.com/' )
1208 u = User.new( :identity_url => 'http://example.com/' )
1209 assert_equal normalized_open_id_url, u.identity_url
1209 assert_equal normalized_open_id_url, u.identity_url
1210 end
1210 end
1211
1211
1212 def test_setting_identity_url_without_trailing_slash
1212 def test_setting_identity_url_without_trailing_slash
1213 normalized_open_id_url = 'http://example.com/'
1213 normalized_open_id_url = 'http://example.com/'
1214 u = User.new( :identity_url => 'http://example.com' )
1214 u = User.new( :identity_url => 'http://example.com' )
1215 assert_equal normalized_open_id_url, u.identity_url
1215 assert_equal normalized_open_id_url, u.identity_url
1216 end
1216 end
1217
1217
1218 def test_setting_identity_url_without_protocol
1218 def test_setting_identity_url_without_protocol
1219 normalized_open_id_url = 'http://example.com/'
1219 normalized_open_id_url = 'http://example.com/'
1220 u = User.new( :identity_url => 'example.com' )
1220 u = User.new( :identity_url => 'example.com' )
1221 assert_equal normalized_open_id_url, u.identity_url
1221 assert_equal normalized_open_id_url, u.identity_url
1222 end
1222 end
1223
1223
1224 def test_setting_blank_identity_url
1224 def test_setting_blank_identity_url
1225 u = User.new( :identity_url => 'example.com' )
1225 u = User.new( :identity_url => 'example.com' )
1226 u.identity_url = ''
1226 u.identity_url = ''
1227 assert u.identity_url.blank?
1227 assert u.identity_url.blank?
1228 end
1228 end
1229
1229
1230 def test_setting_invalid_identity_url
1230 def test_setting_invalid_identity_url
1231 u = User.new( :identity_url => 'this is not an openid url' )
1231 u = User.new( :identity_url => 'this is not an openid url' )
1232 assert u.identity_url.blank?
1232 assert u.identity_url.blank?
1233 end
1233 end
1234 else
1234 else
1235 puts "Skipping openid tests."
1235 puts "Skipping openid tests."
1236 end
1236 end
1237 end
1237 end
@@ -1,201 +1,201
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class WatcherTest < ActiveSupport::TestCase
20 class WatcherTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules,
22 :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers,
22 :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers,
23 :boards, :messages,
23 :boards, :messages,
24 :wikis, :wiki_pages,
24 :wikis, :wiki_pages,
25 :watchers
25 :watchers
26
26
27 def setup
27 def setup
28 @user = User.find(1)
28 @user = User.find(1)
29 @issue = Issue.find(1)
29 @issue = Issue.find(1)
30 end
30 end
31
31
32 def test_validate
32 def test_validate
33 user = User.find(5)
33 user = User.find(5)
34 assert !user.active?
34 assert !user.active?
35 watcher = Watcher.new(:user_id => user.id)
35 watcher = Watcher.new(:user_id => user.id)
36 assert !watcher.save
36 assert !watcher.save
37 end
37 end
38
38
39 def test_watch
39 def test_watch
40 assert @issue.add_watcher(@user)
40 assert @issue.add_watcher(@user)
41 @issue.reload
41 @issue.reload
42 assert @issue.watchers.detect { |w| w.user == @user }
42 assert @issue.watchers.detect { |w| w.user == @user }
43 end
43 end
44
44
45 def test_cant_watch_twice
45 def test_cant_watch_twice
46 assert @issue.add_watcher(@user)
46 assert @issue.add_watcher(@user)
47 assert !@issue.add_watcher(@user)
47 assert !@issue.add_watcher(@user)
48 end
48 end
49
49
50 def test_watched_by
50 def test_watched_by
51 assert @issue.add_watcher(@user)
51 assert @issue.add_watcher(@user)
52 @issue.reload
52 @issue.reload
53 assert @issue.watched_by?(@user)
53 assert @issue.watched_by?(@user)
54 assert Issue.watched_by(@user).include?(@issue)
54 assert Issue.watched_by(@user).include?(@issue)
55 end
55 end
56
56
57 def test_watcher_users
57 def test_watcher_users
58 watcher_users = Issue.find(2).watcher_users
58 watcher_users = Issue.find(2).watcher_users
59 assert_kind_of Array, watcher_users.collect{|w| w}
59 assert_kind_of Array, watcher_users.collect{|w| w}
60 assert_kind_of User, watcher_users.first
60 assert_kind_of User, watcher_users.first
61 end
61 end
62
62
63 def test_watcher_users_should_be_reloaded_after_adding_a_watcher
63 def test_watcher_users_should_be_reloaded_after_adding_a_watcher
64 issue = Issue.find(2)
64 issue = Issue.find(2)
65 user = User.generate!
65 user = User.generate!
66
66
67 assert_difference 'issue.watcher_users.to_a.size' do
67 assert_difference 'issue.watcher_users.to_a.size' do
68 issue.add_watcher user
68 issue.add_watcher user
69 end
69 end
70 end
70 end
71
71
72 def test_watcher_users_should_not_validate_user
72 def test_watcher_users_should_not_validate_user
73 User.where(:id => 1).update_all("firstname = ''")
73 User.where(:id => 1).update_all("firstname = ''")
74 @user.reload
74 @user.reload
75 assert !@user.valid?
75 assert !@user.valid?
76
76
77 issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2))
77 issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2))
78 issue.watcher_users << @user
78 issue.watcher_users << @user
79 issue.save!
79 issue.save!
80 assert issue.watched_by?(@user)
80 assert issue.watched_by?(@user)
81 end
81 end
82
82
83 def test_watcher_user_ids
83 def test_watcher_user_ids
84 assert_equal [1, 3], Issue.find(2).watcher_user_ids.sort
84 assert_equal [1, 3], Issue.find(2).watcher_user_ids.sort
85 end
85 end
86
86
87 def test_watcher_user_ids=
87 def test_watcher_user_ids=
88 issue = Issue.new
88 issue = Issue.new
89 issue.watcher_user_ids = ['1', '3']
89 issue.watcher_user_ids = ['1', '3']
90 assert issue.watched_by?(User.find(1))
90 assert issue.watched_by?(User.find(1))
91 end
91 end
92
92
93 def test_watcher_user_ids_should_make_ids_uniq
93 def test_watcher_user_ids_should_make_ids_uniq
94 issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2))
94 issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2))
95 issue.watcher_user_ids = ['1', '3', '1']
95 issue.watcher_user_ids = ['1', '3', '1']
96 issue.save!
96 issue.save!
97 assert_equal 2, issue.watchers.count
97 assert_equal 2, issue.watchers.count
98 end
98 end
99
99
100 def test_addable_watcher_users
100 def test_addable_watcher_users
101 addable_watcher_users = @issue.addable_watcher_users
101 addable_watcher_users = @issue.addable_watcher_users
102 assert_kind_of Array, addable_watcher_users
102 assert_kind_of Array, addable_watcher_users
103 assert_kind_of User, addable_watcher_users.first
103 assert_kind_of User, addable_watcher_users.first
104 end
104 end
105
105
106 def test_addable_watcher_users_should_not_include_user_that_cannot_view_the_object
106 def test_addable_watcher_users_should_not_include_user_that_cannot_view_the_object
107 issue = Issue.new(:project => Project.find(1), :is_private => true)
107 issue = Issue.new(:project => Project.find(1), :is_private => true)
108 assert_nil issue.addable_watcher_users.detect {|user| !issue.visible?(user)}
108 assert_nil issue.addable_watcher_users.detect {|user| !issue.visible?(user)}
109 end
109 end
110
110
111 def test_any_watched_should_return_false_if_no_object_is_watched
111 def test_any_watched_should_return_false_if_no_object_is_watched
112 objects = (0..2).map {Issue.generate!}
112 objects = (0..2).map {Issue.generate!}
113
113
114 assert_equal false, Watcher.any_watched?(objects, @user)
114 assert_equal false, Watcher.any_watched?(objects, @user)
115 end
115 end
116
116
117 def test_any_watched_should_return_true_if_one_object_is_watched
117 def test_any_watched_should_return_true_if_one_object_is_watched
118 objects = (0..2).map {Issue.generate!}
118 objects = (0..2).map {Issue.generate!}
119 objects.last.add_watcher(@user)
119 objects.last.add_watcher(@user)
120
120
121 assert_equal true, Watcher.any_watched?(objects, @user)
121 assert_equal true, Watcher.any_watched?(objects, @user)
122 end
122 end
123
123
124 def test_any_watched_should_return_false_with_no_object
124 def test_any_watched_should_return_false_with_no_object
125 assert_equal false, Watcher.any_watched?([], @user)
125 assert_equal false, Watcher.any_watched?([], @user)
126 end
126 end
127
127
128 def test_recipients
128 def test_recipients
129 @issue.watchers.delete_all
129 @issue.watchers.delete_all
130 @issue.reload
130 @issue.reload
131
131
132 assert @issue.watcher_recipients.empty?
132 assert @issue.watcher_recipients.empty?
133 assert @issue.add_watcher(@user)
133 assert @issue.add_watcher(@user)
134
134
135 @user.mail_notification = 'all'
135 @user.mail_notification = 'all'
136 @user.save!
136 @user.save!
137 @issue.reload
137 @issue.reload
138 assert @issue.watcher_recipients.include?(@user.mail)
138 assert @issue.watcher_recipients.include?(@user.mail)
139
139
140 @user.mail_notification = 'none'
140 @user.mail_notification = 'none'
141 @user.save!
141 @user.save!
142 @issue.reload
142 @issue.reload
143 assert !@issue.watcher_recipients.include?(@user.mail)
143 assert !@issue.watcher_recipients.include?(@user.mail)
144 end
144 end
145
145
146 def test_unwatch
146 def test_unwatch
147 assert @issue.add_watcher(@user)
147 assert @issue.add_watcher(@user)
148 @issue.reload
148 @issue.reload
149 assert_equal 1, @issue.remove_watcher(@user)
149 assert_equal 1, @issue.remove_watcher(@user)
150 end
150 end
151
151
152 def test_prune_with_user
152 def test_prune_with_user
153 Watcher.delete_all("user_id = 9")
153 Watcher.where("user_id = 9").delete_all
154 user = User.find(9)
154 user = User.find(9)
155
155
156 # public
156 # public
157 Watcher.create!(:watchable => Issue.find(1), :user => user)
157 Watcher.create!(:watchable => Issue.find(1), :user => user)
158 Watcher.create!(:watchable => Issue.find(2), :user => user)
158 Watcher.create!(:watchable => Issue.find(2), :user => user)
159 Watcher.create!(:watchable => Message.find(1), :user => user)
159 Watcher.create!(:watchable => Message.find(1), :user => user)
160 Watcher.create!(:watchable => Wiki.find(1), :user => user)
160 Watcher.create!(:watchable => Wiki.find(1), :user => user)
161 Watcher.create!(:watchable => WikiPage.find(2), :user => user)
161 Watcher.create!(:watchable => WikiPage.find(2), :user => user)
162
162
163 # private project (id: 2)
163 # private project (id: 2)
164 Member.create!(:project => Project.find(2), :principal => user, :role_ids => [1])
164 Member.create!(:project => Project.find(2), :principal => user, :role_ids => [1])
165 Watcher.create!(:watchable => Issue.find(4), :user => user)
165 Watcher.create!(:watchable => Issue.find(4), :user => user)
166 Watcher.create!(:watchable => Message.find(7), :user => user)
166 Watcher.create!(:watchable => Message.find(7), :user => user)
167 Watcher.create!(:watchable => Wiki.find(2), :user => user)
167 Watcher.create!(:watchable => Wiki.find(2), :user => user)
168 Watcher.create!(:watchable => WikiPage.find(3), :user => user)
168 Watcher.create!(:watchable => WikiPage.find(3), :user => user)
169
169
170 assert_no_difference 'Watcher.count' do
170 assert_no_difference 'Watcher.count' do
171 Watcher.prune(:user => User.find(9))
171 Watcher.prune(:user => User.find(9))
172 end
172 end
173
173
174 Member.delete_all
174 Member.delete_all
175
175
176 assert_difference 'Watcher.count', -4 do
176 assert_difference 'Watcher.count', -4 do
177 Watcher.prune(:user => User.find(9))
177 Watcher.prune(:user => User.find(9))
178 end
178 end
179
179
180 assert Issue.find(1).watched_by?(user)
180 assert Issue.find(1).watched_by?(user)
181 assert !Issue.find(4).watched_by?(user)
181 assert !Issue.find(4).watched_by?(user)
182 end
182 end
183
183
184 def test_prune_with_project
184 def test_prune_with_project
185 user = User.find(9)
185 user = User.find(9)
186 Watcher.new(:watchable => Issue.find(4), :user => User.find(9)).save(:validate => false) # project 2
186 Watcher.new(:watchable => Issue.find(4), :user => User.find(9)).save(:validate => false) # project 2
187 Watcher.new(:watchable => Issue.find(6), :user => User.find(9)).save(:validate => false) # project 5
187 Watcher.new(:watchable => Issue.find(6), :user => User.find(9)).save(:validate => false) # project 5
188
188
189 assert Watcher.prune(:project => Project.find(5)) > 0
189 assert Watcher.prune(:project => Project.find(5)) > 0
190 assert Issue.find(4).watched_by?(user)
190 assert Issue.find(4).watched_by?(user)
191 assert !Issue.find(6).watched_by?(user)
191 assert !Issue.find(6).watched_by?(user)
192 end
192 end
193
193
194 def test_prune_all
194 def test_prune_all
195 user = User.find(9)
195 user = User.find(9)
196 Watcher.new(:watchable => Issue.find(4), :user => User.find(9)).save(:validate => false)
196 Watcher.new(:watchable => Issue.find(4), :user => User.find(9)).save(:validate => false)
197
197
198 assert Watcher.prune > 0
198 assert Watcher.prune > 0
199 assert !Issue.find(4).watched_by?(user)
199 assert !Issue.find(4).watched_by?(user)
200 end
200 end
201 end
201 end
General Comments 0
You need to be logged in to leave comments. Login now