##// END OF EJS Templates
Merged r9689 from trunk....
Jean-Philippe Lang -
r9507:e62a40a71947
parent child
Show More
@@ -1,535 +1,524
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
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
25
26 layout 'base'
26 layout 'base'
27 exempt_from_layout 'builder', 'rsb'
27 exempt_from_layout 'builder', 'rsb'
28
28
29 protect_from_forgery
29 protect_from_forgery
30 def handle_unverified_request
30 def handle_unverified_request
31 super
31 super
32 cookies.delete(:autologin)
32 cookies.delete(:autologin)
33 end
33 end
34 # Remove broken cookie after upgrade from 0.8.x (#4292)
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
36 # TODO: remove it when Rails is fixed
37 before_filter :delete_broken_cookies
38 def delete_broken_cookies
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
40 cookies.delete '_redmine_session'
41 redirect_to home_path
42 return false
43 end
44 end
45
34
46 # FIXME: Remove this when all of Rack and Rails have learned how to
35 # FIXME: Remove this when all of Rack and Rails have learned how to
47 # properly use encodings
36 # properly use encodings
48 before_filter :params_filter
37 before_filter :params_filter
49
38
50 def params_filter
39 def params_filter
51 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
40 if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
52 self.utf8nize!(params)
41 self.utf8nize!(params)
53 end
42 end
54 end
43 end
55
44
56 def utf8nize!(obj)
45 def utf8nize!(obj)
57 if obj.frozen?
46 if obj.frozen?
58 obj
47 obj
59 elsif obj.is_a? String
48 elsif obj.is_a? String
60 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
49 obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
61 elsif obj.is_a? Hash
50 elsif obj.is_a? Hash
62 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
51 obj.each {|k, v| obj[k] = self.utf8nize!(v)}
63 elsif obj.is_a? Array
52 elsif obj.is_a? Array
64 obj.each {|v| self.utf8nize!(v)}
53 obj.each {|v| self.utf8nize!(v)}
65 else
54 else
66 obj
55 obj
67 end
56 end
68 end
57 end
69
58
70 before_filter :user_setup, :check_if_login_required, :set_localization
59 before_filter :user_setup, :check_if_login_required, :set_localization
71 filter_parameter_logging :password
60 filter_parameter_logging :password
72
61
73 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
62 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
74 rescue_from ::Unauthorized, :with => :deny_access
63 rescue_from ::Unauthorized, :with => :deny_access
75
64
76 include Redmine::Search::Controller
65 include Redmine::Search::Controller
77 include Redmine::MenuManager::MenuController
66 include Redmine::MenuManager::MenuController
78 helper Redmine::MenuManager::MenuHelper
67 helper Redmine::MenuManager::MenuHelper
79
68
80 Redmine::Scm::Base.all.each do |scm|
69 Redmine::Scm::Base.all.each do |scm|
81 require_dependency "repository/#{scm.underscore}"
70 require_dependency "repository/#{scm.underscore}"
82 end
71 end
83
72
84 def user_setup
73 def user_setup
85 # Check the settings cache for each request
74 # Check the settings cache for each request
86 Setting.check_cache
75 Setting.check_cache
87 # Find the current user
76 # Find the current user
88 User.current = find_current_user
77 User.current = find_current_user
89 end
78 end
90
79
91 # Returns the current user or nil if no user is logged in
80 # Returns the current user or nil if no user is logged in
92 # and starts a session if needed
81 # and starts a session if needed
93 def find_current_user
82 def find_current_user
94 if session[:user_id]
83 if session[:user_id]
95 # existing session
84 # existing session
96 (User.active.find(session[:user_id]) rescue nil)
85 (User.active.find(session[:user_id]) rescue nil)
97 elsif cookies[:autologin] && Setting.autologin?
86 elsif cookies[:autologin] && Setting.autologin?
98 # auto-login feature starts a new session
87 # auto-login feature starts a new session
99 user = User.try_to_autologin(cookies[:autologin])
88 user = User.try_to_autologin(cookies[:autologin])
100 session[:user_id] = user.id if user
89 session[:user_id] = user.id if user
101 user
90 user
102 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
91 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
103 # RSS key authentication does not start a session
92 # RSS key authentication does not start a session
104 User.find_by_rss_key(params[:key])
93 User.find_by_rss_key(params[:key])
105 elsif Setting.rest_api_enabled? && accept_api_auth?
94 elsif Setting.rest_api_enabled? && accept_api_auth?
106 if (key = api_key_from_request)
95 if (key = api_key_from_request)
107 # Use API key
96 # Use API key
108 User.find_by_api_key(key)
97 User.find_by_api_key(key)
109 else
98 else
110 # HTTP Basic, either username/password or API key/random
99 # HTTP Basic, either username/password or API key/random
111 authenticate_with_http_basic do |username, password|
100 authenticate_with_http_basic do |username, password|
112 User.try_to_login(username, password) || User.find_by_api_key(username)
101 User.try_to_login(username, password) || User.find_by_api_key(username)
113 end
102 end
114 end
103 end
115 end
104 end
116 end
105 end
117
106
118 # Sets the logged in user
107 # Sets the logged in user
119 def logged_user=(user)
108 def logged_user=(user)
120 reset_session
109 reset_session
121 if user && user.is_a?(User)
110 if user && user.is_a?(User)
122 User.current = user
111 User.current = user
123 session[:user_id] = user.id
112 session[:user_id] = user.id
124 else
113 else
125 User.current = User.anonymous
114 User.current = User.anonymous
126 end
115 end
127 end
116 end
128
117
129 # Logs out current user
118 # Logs out current user
130 def logout_user
119 def logout_user
131 if User.current.logged?
120 if User.current.logged?
132 cookies.delete :autologin
121 cookies.delete :autologin
133 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
122 Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
134 self.logged_user = nil
123 self.logged_user = nil
135 end
124 end
136 end
125 end
137
126
138 # check if login is globally required to access the application
127 # check if login is globally required to access the application
139 def check_if_login_required
128 def check_if_login_required
140 # no check needed if user is already logged in
129 # no check needed if user is already logged in
141 return true if User.current.logged?
130 return true if User.current.logged?
142 require_login if Setting.login_required?
131 require_login if Setting.login_required?
143 end
132 end
144
133
145 def set_localization
134 def set_localization
146 lang = nil
135 lang = nil
147 if User.current.logged?
136 if User.current.logged?
148 lang = find_language(User.current.language)
137 lang = find_language(User.current.language)
149 end
138 end
150 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
139 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
151 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
140 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
152 if !accept_lang.blank?
141 if !accept_lang.blank?
153 accept_lang = accept_lang.downcase
142 accept_lang = accept_lang.downcase
154 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
143 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
155 end
144 end
156 end
145 end
157 lang ||= Setting.default_language
146 lang ||= Setting.default_language
158 set_language_if_valid(lang)
147 set_language_if_valid(lang)
159 end
148 end
160
149
161 def require_login
150 def require_login
162 if !User.current.logged?
151 if !User.current.logged?
163 # Extract only the basic url parameters on non-GET requests
152 # Extract only the basic url parameters on non-GET requests
164 if request.get?
153 if request.get?
165 url = url_for(params)
154 url = url_for(params)
166 else
155 else
167 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
156 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
168 end
157 end
169 respond_to do |format|
158 respond_to do |format|
170 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
159 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
171 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
160 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
172 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
161 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
173 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
162 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
174 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
163 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
175 end
164 end
176 return false
165 return false
177 end
166 end
178 true
167 true
179 end
168 end
180
169
181 def require_admin
170 def require_admin
182 return unless require_login
171 return unless require_login
183 if !User.current.admin?
172 if !User.current.admin?
184 render_403
173 render_403
185 return false
174 return false
186 end
175 end
187 true
176 true
188 end
177 end
189
178
190 def deny_access
179 def deny_access
191 User.current.logged? ? render_403 : require_login
180 User.current.logged? ? render_403 : require_login
192 end
181 end
193
182
194 # Authorize the user for the requested action
183 # Authorize the user for the requested action
195 def authorize(ctrl = params[:controller], action = params[:action], global = false)
184 def authorize(ctrl = params[:controller], action = params[:action], global = false)
196 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
185 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
197 if allowed
186 if allowed
198 true
187 true
199 else
188 else
200 if @project && @project.archived?
189 if @project && @project.archived?
201 render_403 :message => :notice_not_authorized_archived_project
190 render_403 :message => :notice_not_authorized_archived_project
202 else
191 else
203 deny_access
192 deny_access
204 end
193 end
205 end
194 end
206 end
195 end
207
196
208 # Authorize the user for the requested action outside a project
197 # Authorize the user for the requested action outside a project
209 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
198 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
210 authorize(ctrl, action, global)
199 authorize(ctrl, action, global)
211 end
200 end
212
201
213 # Find project of id params[:id]
202 # Find project of id params[:id]
214 def find_project
203 def find_project
215 @project = Project.find(params[:id])
204 @project = Project.find(params[:id])
216 rescue ActiveRecord::RecordNotFound
205 rescue ActiveRecord::RecordNotFound
217 render_404
206 render_404
218 end
207 end
219
208
220 # Find project of id params[:project_id]
209 # Find project of id params[:project_id]
221 def find_project_by_project_id
210 def find_project_by_project_id
222 @project = Project.find(params[:project_id])
211 @project = Project.find(params[:project_id])
223 rescue ActiveRecord::RecordNotFound
212 rescue ActiveRecord::RecordNotFound
224 render_404
213 render_404
225 end
214 end
226
215
227 # Find a project based on params[:project_id]
216 # Find a project based on params[:project_id]
228 # TODO: some subclasses override this, see about merging their logic
217 # TODO: some subclasses override this, see about merging their logic
229 def find_optional_project
218 def find_optional_project
230 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
219 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
231 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
220 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
232 allowed ? true : deny_access
221 allowed ? true : deny_access
233 rescue ActiveRecord::RecordNotFound
222 rescue ActiveRecord::RecordNotFound
234 render_404
223 render_404
235 end
224 end
236
225
237 # Finds and sets @project based on @object.project
226 # Finds and sets @project based on @object.project
238 def find_project_from_association
227 def find_project_from_association
239 render_404 unless @object.present?
228 render_404 unless @object.present?
240
229
241 @project = @object.project
230 @project = @object.project
242 end
231 end
243
232
244 def find_model_object
233 def find_model_object
245 model = self.class.read_inheritable_attribute('model_object')
234 model = self.class.read_inheritable_attribute('model_object')
246 if model
235 if model
247 @object = model.find(params[:id])
236 @object = model.find(params[:id])
248 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
237 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
249 end
238 end
250 rescue ActiveRecord::RecordNotFound
239 rescue ActiveRecord::RecordNotFound
251 render_404
240 render_404
252 end
241 end
253
242
254 def self.model_object(model)
243 def self.model_object(model)
255 write_inheritable_attribute('model_object', model)
244 write_inheritable_attribute('model_object', model)
256 end
245 end
257
246
258 # Filter for bulk issue operations
247 # Filter for bulk issue operations
259 def find_issues
248 def find_issues
260 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
249 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
261 raise ActiveRecord::RecordNotFound if @issues.empty?
250 raise ActiveRecord::RecordNotFound if @issues.empty?
262 if @issues.detect {|issue| !issue.visible?}
251 if @issues.detect {|issue| !issue.visible?}
263 deny_access
252 deny_access
264 return
253 return
265 end
254 end
266 @projects = @issues.collect(&:project).compact.uniq
255 @projects = @issues.collect(&:project).compact.uniq
267 @project = @projects.first if @projects.size == 1
256 @project = @projects.first if @projects.size == 1
268 rescue ActiveRecord::RecordNotFound
257 rescue ActiveRecord::RecordNotFound
269 render_404
258 render_404
270 end
259 end
271
260
272 # make sure that the user is a member of the project (or admin) if project is private
261 # make sure that the user is a member of the project (or admin) if project is private
273 # used as a before_filter for actions that do not require any particular permission on the project
262 # used as a before_filter for actions that do not require any particular permission on the project
274 def check_project_privacy
263 def check_project_privacy
275 if @project && @project.active?
264 if @project && @project.active?
276 if @project.visible?
265 if @project.visible?
277 true
266 true
278 else
267 else
279 deny_access
268 deny_access
280 end
269 end
281 else
270 else
282 @project = nil
271 @project = nil
283 render_404
272 render_404
284 false
273 false
285 end
274 end
286 end
275 end
287
276
288 def back_url
277 def back_url
289 params[:back_url] || request.env['HTTP_REFERER']
278 params[:back_url] || request.env['HTTP_REFERER']
290 end
279 end
291
280
292 def redirect_back_or_default(default)
281 def redirect_back_or_default(default)
293 back_url = CGI.unescape(params[:back_url].to_s)
282 back_url = CGI.unescape(params[:back_url].to_s)
294 if !back_url.blank?
283 if !back_url.blank?
295 begin
284 begin
296 uri = URI.parse(back_url)
285 uri = URI.parse(back_url)
297 # do not redirect user to another host or to the login or register page
286 # do not redirect user to another host or to the login or register page
298 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
287 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
299 redirect_to(back_url)
288 redirect_to(back_url)
300 return
289 return
301 end
290 end
302 rescue URI::InvalidURIError
291 rescue URI::InvalidURIError
303 # redirect to default
292 # redirect to default
304 end
293 end
305 end
294 end
306 redirect_to default
295 redirect_to default
307 false
296 false
308 end
297 end
309
298
310 def render_403(options={})
299 def render_403(options={})
311 @project = nil
300 @project = nil
312 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
301 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
313 return false
302 return false
314 end
303 end
315
304
316 def render_404(options={})
305 def render_404(options={})
317 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
306 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
318 return false
307 return false
319 end
308 end
320
309
321 # Renders an error response
310 # Renders an error response
322 def render_error(arg)
311 def render_error(arg)
323 arg = {:message => arg} unless arg.is_a?(Hash)
312 arg = {:message => arg} unless arg.is_a?(Hash)
324
313
325 @message = arg[:message]
314 @message = arg[:message]
326 @message = l(@message) if @message.is_a?(Symbol)
315 @message = l(@message) if @message.is_a?(Symbol)
327 @status = arg[:status] || 500
316 @status = arg[:status] || 500
328
317
329 respond_to do |format|
318 respond_to do |format|
330 format.html {
319 format.html {
331 render :template => 'common/error', :layout => use_layout, :status => @status
320 render :template => 'common/error', :layout => use_layout, :status => @status
332 }
321 }
333 format.atom { head @status }
322 format.atom { head @status }
334 format.xml { head @status }
323 format.xml { head @status }
335 format.js { head @status }
324 format.js { head @status }
336 format.json { head @status }
325 format.json { head @status }
337 end
326 end
338 end
327 end
339
328
340 # Filter for actions that provide an API response
329 # Filter for actions that provide an API response
341 # but have no HTML representation for non admin users
330 # but have no HTML representation for non admin users
342 def require_admin_or_api_request
331 def require_admin_or_api_request
343 return true if api_request?
332 return true if api_request?
344 if User.current.admin?
333 if User.current.admin?
345 true
334 true
346 elsif User.current.logged?
335 elsif User.current.logged?
347 render_error(:status => 406)
336 render_error(:status => 406)
348 else
337 else
349 deny_access
338 deny_access
350 end
339 end
351 end
340 end
352
341
353 # Picks which layout to use based on the request
342 # Picks which layout to use based on the request
354 #
343 #
355 # @return [boolean, string] name of the layout to use or false for no layout
344 # @return [boolean, string] name of the layout to use or false for no layout
356 def use_layout
345 def use_layout
357 request.xhr? ? false : 'base'
346 request.xhr? ? false : 'base'
358 end
347 end
359
348
360 def invalid_authenticity_token
349 def invalid_authenticity_token
361 if api_request?
350 if api_request?
362 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
351 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
363 end
352 end
364 render_error "Invalid form authenticity token."
353 render_error "Invalid form authenticity token."
365 end
354 end
366
355
367 def render_feed(items, options={})
356 def render_feed(items, options={})
368 @items = items || []
357 @items = items || []
369 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
358 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
370 @items = @items.slice(0, Setting.feeds_limit.to_i)
359 @items = @items.slice(0, Setting.feeds_limit.to_i)
371 @title = options[:title] || Setting.app_title
360 @title = options[:title] || Setting.app_title
372 render :template => "common/feed.atom", :layout => false,
361 render :template => "common/feed.atom", :layout => false,
373 :content_type => 'application/atom+xml'
362 :content_type => 'application/atom+xml'
374 end
363 end
375
364
376 def self.accept_rss_auth(*actions)
365 def self.accept_rss_auth(*actions)
377 if actions.any?
366 if actions.any?
378 write_inheritable_attribute('accept_rss_auth_actions', actions)
367 write_inheritable_attribute('accept_rss_auth_actions', actions)
379 else
368 else
380 read_inheritable_attribute('accept_rss_auth_actions') || []
369 read_inheritable_attribute('accept_rss_auth_actions') || []
381 end
370 end
382 end
371 end
383
372
384 def accept_rss_auth?(action=action_name)
373 def accept_rss_auth?(action=action_name)
385 self.class.accept_rss_auth.include?(action.to_sym)
374 self.class.accept_rss_auth.include?(action.to_sym)
386 end
375 end
387
376
388 def self.accept_api_auth(*actions)
377 def self.accept_api_auth(*actions)
389 if actions.any?
378 if actions.any?
390 write_inheritable_attribute('accept_api_auth_actions', actions)
379 write_inheritable_attribute('accept_api_auth_actions', actions)
391 else
380 else
392 read_inheritable_attribute('accept_api_auth_actions') || []
381 read_inheritable_attribute('accept_api_auth_actions') || []
393 end
382 end
394 end
383 end
395
384
396 def accept_api_auth?(action=action_name)
385 def accept_api_auth?(action=action_name)
397 self.class.accept_api_auth.include?(action.to_sym)
386 self.class.accept_api_auth.include?(action.to_sym)
398 end
387 end
399
388
400 # Returns the number of objects that should be displayed
389 # Returns the number of objects that should be displayed
401 # on the paginated list
390 # on the paginated list
402 def per_page_option
391 def per_page_option
403 per_page = nil
392 per_page = nil
404 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
393 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
405 per_page = params[:per_page].to_s.to_i
394 per_page = params[:per_page].to_s.to_i
406 session[:per_page] = per_page
395 session[:per_page] = per_page
407 elsif session[:per_page]
396 elsif session[:per_page]
408 per_page = session[:per_page]
397 per_page = session[:per_page]
409 else
398 else
410 per_page = Setting.per_page_options_array.first || 25
399 per_page = Setting.per_page_options_array.first || 25
411 end
400 end
412 per_page
401 per_page
413 end
402 end
414
403
415 # Returns offset and limit used to retrieve objects
404 # Returns offset and limit used to retrieve objects
416 # for an API response based on offset, limit and page parameters
405 # for an API response based on offset, limit and page parameters
417 def api_offset_and_limit(options=params)
406 def api_offset_and_limit(options=params)
418 if options[:offset].present?
407 if options[:offset].present?
419 offset = options[:offset].to_i
408 offset = options[:offset].to_i
420 if offset < 0
409 if offset < 0
421 offset = 0
410 offset = 0
422 end
411 end
423 end
412 end
424 limit = options[:limit].to_i
413 limit = options[:limit].to_i
425 if limit < 1
414 if limit < 1
426 limit = 25
415 limit = 25
427 elsif limit > 100
416 elsif limit > 100
428 limit = 100
417 limit = 100
429 end
418 end
430 if offset.nil? && options[:page].present?
419 if offset.nil? && options[:page].present?
431 offset = (options[:page].to_i - 1) * limit
420 offset = (options[:page].to_i - 1) * limit
432 offset = 0 if offset < 0
421 offset = 0 if offset < 0
433 end
422 end
434 offset ||= 0
423 offset ||= 0
435
424
436 [offset, limit]
425 [offset, limit]
437 end
426 end
438
427
439 # qvalues http header parser
428 # qvalues http header parser
440 # code taken from webrick
429 # code taken from webrick
441 def parse_qvalues(value)
430 def parse_qvalues(value)
442 tmp = []
431 tmp = []
443 if value
432 if value
444 parts = value.split(/,\s*/)
433 parts = value.split(/,\s*/)
445 parts.each {|part|
434 parts.each {|part|
446 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
435 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
447 val = m[1]
436 val = m[1]
448 q = (m[2] or 1).to_f
437 q = (m[2] or 1).to_f
449 tmp.push([val, q])
438 tmp.push([val, q])
450 end
439 end
451 }
440 }
452 tmp = tmp.sort_by{|val, q| -q}
441 tmp = tmp.sort_by{|val, q| -q}
453 tmp.collect!{|val, q| val}
442 tmp.collect!{|val, q| val}
454 end
443 end
455 return tmp
444 return tmp
456 rescue
445 rescue
457 nil
446 nil
458 end
447 end
459
448
460 # Returns a string that can be used as filename value in Content-Disposition header
449 # Returns a string that can be used as filename value in Content-Disposition header
461 def filename_for_content_disposition(name)
450 def filename_for_content_disposition(name)
462 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
451 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
463 end
452 end
464
453
465 def api_request?
454 def api_request?
466 %w(xml json).include? params[:format]
455 %w(xml json).include? params[:format]
467 end
456 end
468
457
469 # Returns the API key present in the request
458 # Returns the API key present in the request
470 def api_key_from_request
459 def api_key_from_request
471 if params[:key].present?
460 if params[:key].present?
472 params[:key]
461 params[:key]
473 elsif request.headers["X-Redmine-API-Key"].present?
462 elsif request.headers["X-Redmine-API-Key"].present?
474 request.headers["X-Redmine-API-Key"]
463 request.headers["X-Redmine-API-Key"]
475 end
464 end
476 end
465 end
477
466
478 # Renders a warning flash if obj has unsaved attachments
467 # Renders a warning flash if obj has unsaved attachments
479 def render_attachment_warning_if_needed(obj)
468 def render_attachment_warning_if_needed(obj)
480 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
469 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
481 end
470 end
482
471
483 # Sets the `flash` notice or error based the number of issues that did not save
472 # Sets the `flash` notice or error based the number of issues that did not save
484 #
473 #
485 # @param [Array, Issue] issues all of the saved and unsaved Issues
474 # @param [Array, Issue] issues all of the saved and unsaved Issues
486 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
475 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
487 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
476 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
488 if unsaved_issue_ids.empty?
477 if unsaved_issue_ids.empty?
489 flash[:notice] = l(:notice_successful_update) unless issues.empty?
478 flash[:notice] = l(:notice_successful_update) unless issues.empty?
490 else
479 else
491 flash[:error] = l(:notice_failed_to_save_issues,
480 flash[:error] = l(:notice_failed_to_save_issues,
492 :count => unsaved_issue_ids.size,
481 :count => unsaved_issue_ids.size,
493 :total => issues.size,
482 :total => issues.size,
494 :ids => '#' + unsaved_issue_ids.join(', #'))
483 :ids => '#' + unsaved_issue_ids.join(', #'))
495 end
484 end
496 end
485 end
497
486
498 # Rescues an invalid query statement. Just in case...
487 # Rescues an invalid query statement. Just in case...
499 def query_statement_invalid(exception)
488 def query_statement_invalid(exception)
500 logger.error "Query::StatementInvalid: #{exception.message}" if logger
489 logger.error "Query::StatementInvalid: #{exception.message}" if logger
501 session.delete(:query)
490 session.delete(:query)
502 sort_clear if respond_to?(:sort_clear)
491 sort_clear if respond_to?(:sort_clear)
503 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
492 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
504 end
493 end
505
494
506 # Renders API response on validation failure
495 # Renders API response on validation failure
507 def render_validation_errors(objects)
496 def render_validation_errors(objects)
508 if objects.is_a?(Array)
497 if objects.is_a?(Array)
509 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
498 @error_messages = objects.map {|object| object.errors.full_messages}.flatten
510 else
499 else
511 @error_messages = objects.errors.full_messages
500 @error_messages = objects.errors.full_messages
512 end
501 end
513 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
502 render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
514 end
503 end
515
504
516 # Overrides #default_template so that the api template
505 # Overrides #default_template so that the api template
517 # is used automatically if it exists
506 # is used automatically if it exists
518 def default_template(action_name = self.action_name)
507 def default_template(action_name = self.action_name)
519 if api_request?
508 if api_request?
520 begin
509 begin
521 return self.view_paths.find_template(default_template_name(action_name), 'api')
510 return self.view_paths.find_template(default_template_name(action_name), 'api')
522 rescue ::ActionView::MissingTemplate
511 rescue ::ActionView::MissingTemplate
523 # the api template was not found
512 # the api template was not found
524 # fallback to the default behaviour
513 # fallback to the default behaviour
525 end
514 end
526 end
515 end
527 super
516 super
528 end
517 end
529
518
530 # Overrides #pick_layout so that #render with no arguments
519 # Overrides #pick_layout so that #render with no arguments
531 # doesn't use the layout for api requests
520 # doesn't use the layout for api requests
532 def pick_layout(*args)
521 def pick_layout(*args)
533 api_request? ? nil : super
522 api_request? ? nil : super
534 end
523 end
535 end
524 end
General Comments 0
You need to be logged in to leave comments. Login now