annotate app/controllers/application_controller.rb @ 45:65d9e2cabaa3 luisf

Added tipoftheday to the config/settings in order to correct previous issues. Tip of the day is now working correctly. Added the heading strings to the locales files.
author luisf
date Tue, 23 Nov 2010 11:50:01 +0000
parents 94944d00e43c
children aea1779e6f18 af80e5618e9b
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'uri'
Chris@0 19 require 'cgi'
Chris@0 20
Chris@0 21 class ApplicationController < ActionController::Base
Chris@0 22 include Redmine::I18n
Chris@0 23
Chris@0 24 layout 'base'
Chris@0 25 exempt_from_layout 'builder'
Chris@0 26
Chris@0 27 # Remove broken cookie after upgrade from 0.8.x (#4292)
Chris@0 28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
Chris@0 29 # TODO: remove it when Rails is fixed
Chris@0 30 before_filter :delete_broken_cookies
Chris@0 31 def delete_broken_cookies
Chris@0 32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
Chris@0 33 cookies.delete '_redmine_session'
Chris@0 34 redirect_to home_path
Chris@0 35 return false
Chris@0 36 end
Chris@0 37 end
Chris@0 38
Chris@0 39 before_filter :user_setup, :check_if_login_required, :set_localization
Chris@0 40 filter_parameter_logging :password
Chris@0 41 protect_from_forgery
Chris@0 42
Chris@0 43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
Chris@0 44
Chris@0 45 include Redmine::Search::Controller
Chris@0 46 include Redmine::MenuManager::MenuController
Chris@0 47 helper Redmine::MenuManager::MenuHelper
Chris@0 48
Chris@0 49 Redmine::Scm::Base.all.each do |scm|
Chris@0 50 require_dependency "repository/#{scm.underscore}"
Chris@0 51 end
Chris@0 52
Chris@0 53 def user_setup
Chris@0 54 # Check the settings cache for each request
Chris@0 55 Setting.check_cache
Chris@0 56 # Find the current user
Chris@0 57 User.current = find_current_user
Chris@0 58 end
Chris@0 59
Chris@0 60 # Returns the current user or nil if no user is logged in
Chris@0 61 # and starts a session if needed
Chris@0 62 def find_current_user
Chris@0 63 if session[:user_id]
Chris@0 64 # existing session
Chris@0 65 (User.active.find(session[:user_id]) rescue nil)
Chris@0 66 elsif cookies[:autologin] && Setting.autologin?
Chris@0 67 # auto-login feature starts a new session
Chris@0 68 user = User.try_to_autologin(cookies[:autologin])
Chris@0 69 session[:user_id] = user.id if user
Chris@0 70 user
Chris@0 71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
Chris@0 72 # RSS key authentication does not start a session
Chris@0 73 User.find_by_rss_key(params[:key])
Chris@0 74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
Chris@0 75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
Chris@0 76 # Use API key
Chris@0 77 User.find_by_api_key(params[:key])
Chris@0 78 else
Chris@0 79 # HTTP Basic, either username/password or API key/random
Chris@0 80 authenticate_with_http_basic do |username, password|
Chris@0 81 User.try_to_login(username, password) || User.find_by_api_key(username)
Chris@0 82 end
Chris@0 83 end
Chris@0 84 end
Chris@0 85 end
Chris@0 86
Chris@0 87 # Sets the logged in user
Chris@0 88 def logged_user=(user)
Chris@0 89 reset_session
Chris@0 90 if user && user.is_a?(User)
Chris@0 91 User.current = user
Chris@0 92 session[:user_id] = user.id
Chris@0 93 else
Chris@0 94 User.current = User.anonymous
Chris@0 95 end
Chris@0 96 end
Chris@0 97
Chris@0 98 # check if login is globally required to access the application
Chris@0 99 def check_if_login_required
Chris@0 100 # no check needed if user is already logged in
Chris@0 101 return true if User.current.logged?
Chris@0 102 require_login if Setting.login_required?
Chris@0 103 end
Chris@0 104
Chris@0 105 def set_localization
Chris@0 106 lang = nil
Chris@0 107 if User.current.logged?
Chris@0 108 lang = find_language(User.current.language)
Chris@0 109 end
Chris@0 110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
Chris@0 111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
Chris@0 112 if !accept_lang.blank?
Chris@0 113 accept_lang = accept_lang.downcase
Chris@0 114 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
Chris@0 115 end
Chris@0 116 end
Chris@0 117 lang ||= Setting.default_language
Chris@0 118 set_language_if_valid(lang)
Chris@0 119 end
Chris@0 120
Chris@0 121 def require_login
Chris@0 122 if !User.current.logged?
Chris@0 123 # Extract only the basic url parameters on non-GET requests
Chris@0 124 if request.get?
Chris@0 125 url = url_for(params)
Chris@0 126 else
Chris@0 127 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
Chris@0 128 end
Chris@0 129 respond_to do |format|
Chris@0 130 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
Chris@0 131 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
Chris@0 132 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 133 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 134 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
Chris@0 135 end
Chris@0 136 return false
Chris@0 137 end
Chris@0 138 true
Chris@0 139 end
Chris@0 140
Chris@0 141 def require_admin
Chris@0 142 return unless require_login
Chris@0 143 if !User.current.admin?
Chris@0 144 render_403
Chris@0 145 return false
Chris@0 146 end
Chris@0 147 true
Chris@0 148 end
Chris@0 149
Chris@0 150 def deny_access
Chris@0 151 User.current.logged? ? render_403 : require_login
Chris@0 152 end
Chris@0 153
Chris@0 154 # Authorize the user for the requested action
Chris@0 155 def authorize(ctrl = params[:controller], action = params[:action], global = false)
chris@37 156 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
chris@37 157 if allowed
chris@37 158 true
chris@37 159 else
chris@37 160 if @project && @project.archived?
chris@37 161 render_403 :message => :notice_not_authorized_archived_project
chris@37 162 else
chris@37 163 deny_access
chris@37 164 end
chris@37 165 end
Chris@0 166 end
Chris@0 167
Chris@0 168 # Authorize the user for the requested action outside a project
Chris@0 169 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
Chris@0 170 authorize(ctrl, action, global)
Chris@0 171 end
Chris@0 172
Chris@0 173 # Find project of id params[:id]
Chris@0 174 def find_project
Chris@0 175 @project = Project.find(params[:id])
Chris@0 176 rescue ActiveRecord::RecordNotFound
Chris@0 177 render_404
Chris@0 178 end
Chris@0 179
chris@22 180 # Find project of id params[:project_id]
chris@22 181 def find_project_by_project_id
chris@22 182 @project = Project.find(params[:project_id])
chris@22 183 rescue ActiveRecord::RecordNotFound
chris@22 184 render_404
chris@22 185 end
chris@22 186
Chris@0 187 # Find a project based on params[:project_id]
Chris@0 188 # TODO: some subclasses override this, see about merging their logic
Chris@0 189 def find_optional_project
Chris@0 190 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
Chris@0 191 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
Chris@0 192 allowed ? true : deny_access
Chris@0 193 rescue ActiveRecord::RecordNotFound
Chris@0 194 render_404
Chris@0 195 end
Chris@0 196
Chris@0 197 # Finds and sets @project based on @object.project
Chris@0 198 def find_project_from_association
Chris@0 199 render_404 unless @object.present?
Chris@0 200
Chris@0 201 @project = @object.project
Chris@0 202 rescue ActiveRecord::RecordNotFound
Chris@0 203 render_404
Chris@0 204 end
Chris@0 205
Chris@0 206 def find_model_object
Chris@0 207 model = self.class.read_inheritable_attribute('model_object')
Chris@0 208 if model
Chris@0 209 @object = model.find(params[:id])
Chris@0 210 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
Chris@0 211 end
Chris@0 212 rescue ActiveRecord::RecordNotFound
Chris@0 213 render_404
Chris@0 214 end
Chris@0 215
Chris@0 216 def self.model_object(model)
Chris@0 217 write_inheritable_attribute('model_object', model)
Chris@0 218 end
Chris@14 219
Chris@14 220 # Filter for bulk issue operations
Chris@14 221 def find_issues
Chris@14 222 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
Chris@14 223 raise ActiveRecord::RecordNotFound if @issues.empty?
chris@37 224 @projects = @issues.collect(&:project).compact.uniq
chris@37 225 @project = @projects.first if @projects.size == 1
chris@37 226 rescue ActiveRecord::RecordNotFound
chris@37 227 render_404
chris@37 228 end
chris@37 229
chris@37 230 # Check if project is unique before bulk operations
chris@37 231 def check_project_uniqueness
chris@37 232 unless @project
Chris@14 233 # TODO: let users bulk edit/move/destroy issues from different projects
Chris@14 234 render_error 'Can not bulk edit/move/destroy issues from different projects'
Chris@14 235 return false
Chris@14 236 end
Chris@14 237 end
Chris@14 238
Chris@0 239 # make sure that the user is a member of the project (or admin) if project is private
Chris@0 240 # used as a before_filter for actions that do not require any particular permission on the project
Chris@0 241 def check_project_privacy
Chris@0 242 if @project && @project.active?
Chris@0 243 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
Chris@0 244 true
Chris@0 245 else
Chris@0 246 User.current.logged? ? render_403 : require_login
Chris@0 247 end
Chris@0 248 else
Chris@0 249 @project = nil
Chris@0 250 render_404
Chris@0 251 false
Chris@0 252 end
Chris@0 253 end
Chris@0 254
Chris@14 255 def back_url
Chris@14 256 params[:back_url] || request.env['HTTP_REFERER']
Chris@14 257 end
Chris@14 258
Chris@0 259 def redirect_back_or_default(default)
Chris@0 260 back_url = CGI.unescape(params[:back_url].to_s)
Chris@0 261 if !back_url.blank?
Chris@0 262 begin
Chris@0 263 uri = URI.parse(back_url)
Chris@0 264 # do not redirect user to another host or to the login or register page
Chris@0 265 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
Chris@0 266 redirect_to(back_url)
Chris@0 267 return
Chris@0 268 end
Chris@0 269 rescue URI::InvalidURIError
Chris@0 270 # redirect to default
Chris@0 271 end
Chris@0 272 end
Chris@0 273 redirect_to default
Chris@0 274 end
Chris@0 275
chris@37 276 def render_403(options={})
Chris@0 277 @project = nil
chris@37 278 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
Chris@0 279 return false
Chris@0 280 end
Chris@0 281
chris@37 282 def render_404(options={})
chris@37 283 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
Chris@0 284 return false
Chris@0 285 end
Chris@0 286
chris@37 287 # Renders an error response
chris@37 288 def render_error(arg)
chris@37 289 arg = {:message => arg} unless arg.is_a?(Hash)
chris@37 290
chris@37 291 @message = arg[:message]
chris@37 292 @message = l(@message) if @message.is_a?(Symbol)
chris@37 293 @status = arg[:status] || 500
chris@37 294
Chris@0 295 respond_to do |format|
chris@37 296 format.html {
chris@37 297 render :template => 'common/error', :layout => use_layout, :status => @status
Chris@0 298 }
chris@37 299 format.atom { head @status }
chris@37 300 format.xml { head @status }
chris@37 301 format.js { head @status }
chris@37 302 format.json { head @status }
Chris@0 303 end
Chris@0 304 end
Chris@14 305
Chris@14 306 # Picks which layout to use based on the request
Chris@14 307 #
Chris@14 308 # @return [boolean, string] name of the layout to use or false for no layout
Chris@14 309 def use_layout
Chris@14 310 request.xhr? ? false : 'base'
Chris@14 311 end
Chris@0 312
Chris@0 313 def invalid_authenticity_token
Chris@0 314 if api_request?
Chris@0 315 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
Chris@0 316 end
Chris@0 317 render_error "Invalid form authenticity token."
Chris@0 318 end
Chris@0 319
Chris@0 320 def render_feed(items, options={})
Chris@0 321 @items = items || []
Chris@0 322 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
Chris@0 323 @items = @items.slice(0, Setting.feeds_limit.to_i)
Chris@0 324 @title = options[:title] || Setting.app_title
Chris@0 325 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
Chris@0 326 end
Chris@0 327
Chris@0 328 def self.accept_key_auth(*actions)
Chris@0 329 actions = actions.flatten.map(&:to_s)
Chris@0 330 write_inheritable_attribute('accept_key_auth_actions', actions)
Chris@0 331 end
Chris@0 332
Chris@0 333 def accept_key_auth_actions
Chris@0 334 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
Chris@0 335 end
Chris@0 336
Chris@0 337 # Returns the number of objects that should be displayed
Chris@0 338 # on the paginated list
Chris@0 339 def per_page_option
Chris@0 340 per_page = nil
Chris@0 341 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
Chris@0 342 per_page = params[:per_page].to_s.to_i
Chris@0 343 session[:per_page] = per_page
Chris@0 344 elsif session[:per_page]
Chris@0 345 per_page = session[:per_page]
Chris@0 346 else
Chris@0 347 per_page = Setting.per_page_options_array.first || 25
Chris@0 348 end
Chris@0 349 per_page
Chris@0 350 end
Chris@0 351
Chris@0 352 # qvalues http header parser
Chris@0 353 # code taken from webrick
Chris@0 354 def parse_qvalues(value)
Chris@0 355 tmp = []
Chris@0 356 if value
Chris@0 357 parts = value.split(/,\s*/)
Chris@0 358 parts.each {|part|
Chris@0 359 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
Chris@0 360 val = m[1]
Chris@0 361 q = (m[2] or 1).to_f
Chris@0 362 tmp.push([val, q])
Chris@0 363 end
Chris@0 364 }
Chris@0 365 tmp = tmp.sort_by{|val, q| -q}
Chris@0 366 tmp.collect!{|val, q| val}
Chris@0 367 end
Chris@0 368 return tmp
Chris@0 369 rescue
Chris@0 370 nil
Chris@0 371 end
Chris@0 372
Chris@0 373 # Returns a string that can be used as filename value in Content-Disposition header
Chris@0 374 def filename_for_content_disposition(name)
Chris@0 375 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
Chris@0 376 end
Chris@0 377
Chris@0 378 def api_request?
Chris@0 379 %w(xml json).include? params[:format]
Chris@0 380 end
Chris@0 381
Chris@0 382 # Renders a warning flash if obj has unsaved attachments
Chris@0 383 def render_attachment_warning_if_needed(obj)
Chris@0 384 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
Chris@0 385 end
Chris@0 386
Chris@14 387 # Sets the `flash` notice or error based the number of issues that did not save
Chris@14 388 #
Chris@14 389 # @param [Array, Issue] issues all of the saved and unsaved Issues
Chris@14 390 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
Chris@14 391 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
Chris@14 392 if unsaved_issue_ids.empty?
Chris@14 393 flash[:notice] = l(:notice_successful_update) unless issues.empty?
Chris@14 394 else
Chris@14 395 flash[:error] = l(:notice_failed_to_save_issues,
Chris@14 396 :count => unsaved_issue_ids.size,
Chris@14 397 :total => issues.size,
Chris@14 398 :ids => '#' + unsaved_issue_ids.join(', #'))
Chris@14 399 end
Chris@14 400 end
Chris@14 401
Chris@0 402 # Rescues an invalid query statement. Just in case...
Chris@0 403 def query_statement_invalid(exception)
Chris@0 404 logger.error "Query::StatementInvalid: #{exception.message}" if logger
Chris@0 405 session.delete(:query)
Chris@0 406 sort_clear if respond_to?(:sort_clear)
Chris@0 407 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
Chris@0 408 end
Chris@0 409
Chris@0 410 # Converts the errors on an ActiveRecord object into a common JSON format
Chris@0 411 def object_errors_to_json(object)
Chris@0 412 object.errors.collect do |attribute, error|
Chris@0 413 { attribute => error }
Chris@0 414 end.to_json
Chris@0 415 end
Chris@0 416
Chris@0 417 end