app/controllers/application_controller.rb
# frozen_string_literal: true
# Copyright (c) 2008-2013 Michael Dvorkin and contributors.
#
# Fat Free CRM is freely distributable under the terms of MIT license.
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_devise_parameters, if: :devise_controller?
before_action :authenticate_user!
before_action :set_paper_trail_whodunnit
before_action :set_context
before_action :clear_setting_cache
before_action :cors_preflight_check
before_action { hook(:app_before_filter, self) }
after_action { hook(:app_after_filter, self) }
after_action :cors_set_access_control_headers
helper_method :called_from_index_page?, :called_from_landing_page?
helper_method :klass
respond_to :html, only: %i[index show auto_complete]
respond_to :js
respond_to :json, :xml, except: :edit
respond_to :atom, :csv, :rss, :xls, only: :index
rescue_from ActiveRecord::RecordNotFound, with: :respond_to_not_found
rescue_from CanCan::AccessDenied, with: :respond_to_access_denied
include ERB::Util # to give us h and j methods
# Common auto_complete handler for all core controllers.
#----------------------------------------------------------------------------
def auto_complete
@query = params[:term] || ''
@auto_complete = hook(:auto_complete, self, query: @query, user: current_user)
if @auto_complete.empty?
exclude_ids = auto_complete_ids_to_exclude(params[:related])
@auto_complete = klass.my(current_user).text_search(@query).ransack(id_not_in: exclude_ids).result.limit(10)
else
@auto_complete = @auto_complete.last
end
session[:auto_complete] = controller_name.to_sym
respond_to do |format|
format.any(:js, :html) { render partial: 'auto_complete' }
format.json do
results = @auto_complete.map do |a|
{
id: a.id,
text: a.respond_to?(:full_name) ? a.full_name : a.name
}
end
render json: {
results: results
}
end
end
end
# To deal with pre rails 6 users, reset the session and ask them to relogin
rescue_from ArgumentError do |exception|
if request.format.html? && exception.message == "invalid base64"
request.reset_session
redirect_to login_path
else
raise(exception)
end
end
private
#
# In rails 3, the default behaviour for handle_unverified_request is to delete the session
# and continue executing the request. However, we use cookie based authentication and need
# to halt proceedings. In Rails 4, use "protect_from_forgery with: :exception"
# See http://blog.nvisium.com/2014/09/understanding-protectfromforgery.html for more details.
#----------------------------------------------------------------------------
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
end
#
# Takes { related: 'campaigns/7' } or { related: '5' }
# and returns array of object ids that should be excluded from search
# assumes controller_name is a method on 'related' class that returns a collection
#----------------------------------------------------------------------------
def auto_complete_ids_to_exclude(related)
return [] if related.blank?
return [related.to_i].compact unless related.index('/')
related_class, id = related.split('/')
obj = related_class.classify.constantize.find_by_id(id)
if obj&.respond_to?(controller_name)
obj.send(controller_name).map(&:id)
else
[]
end
end
#----------------------------------------------------------------------------
def klass
@klass ||= controller_name.classify.constantize
end
#----------------------------------------------------------------------------
def clear_setting_cache
Setting.clear_cache!
end
#----------------------------------------------------------------------------
def set_context
Time.zone = ActiveSupport::TimeZone[session[:timezone_offset]] if session[:timezone_offset]
if current_user.present? && (locale = current_user.preference[:locale]).present?
I18n.locale = locale
elsif Setting.locale.present?
I18n.locale = Setting.locale
end
end
#----------------------------------------------------------------------------
def set_current_tab(tab = controller_name)
@current_tab = tab
end
#----------------------------------------------------------------------------
def store_location
session[:return_to] = request.fullpath
end
#----------------------------------------------------------------------------
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
#----------------------------------------------------------------------------
def can_signup?
User.can_signup?
end
#----------------------------------------------------------------------------
def called_from_index_page?(controller = controller_name)
request.referer =~ if controller != "tasks"
%r{/#{controller}$}
else
/tasks\?*/
end
end
#----------------------------------------------------------------------------
def called_from_landing_page?(controller = controller_name)
request.referer =~ %r{/#{controller}/\w+}
end
# Proxy current page for any of the controllers by storing it in a session.
#----------------------------------------------------------------------------
def current_page=(page)
p = page.to_i
@current_page = session[:"#{controller_name}_current_page"] = (p.zero? ? 1 : p)
end
#----------------------------------------------------------------------------
def current_page
page = params[:page] || session[:"#{controller_name}_current_page"] || 1
@current_page = page.to_i
end
# Proxy current search query for any of the controllers by storing it in a session.
#----------------------------------------------------------------------------
def current_query=(query)
if session[:"#{controller_name}_current_query"].to_s != query.to_s # nil.to_s == ""
self.current_page = params[:page] # reset paging otherwise results might be hidden, defaults to 1 if nil
end
@current_query = session[:"#{controller_name}_current_query"] = query
end
#----------------------------------------------------------------------------
def current_query
@current_query = params[:query] || session[:"#{controller_name}_current_query"] || ''
end
#----------------------------------------------------------------------------
def asset
controller_name.singularize
end
#----------------------------------------------------------------------------
def respond_to_not_found(*_types)
flash[:warning] = t(:msg_asset_not_available, asset)
respond_to do |format|
format.html { redirect_to(redirection_url) }
format.js { render plain: 'window.location.reload();' }
format.json { render plain: flash[:warning], status: :not_found }
format.xml { render xml: [flash[:warning]], status: :not_found }
end
end
#----------------------------------------------------------------------------
def respond_to_related_not_found(related, *_types)
asset = "note" if asset == "comment"
flash[:warning] = t(:msg_cant_create_related, asset: asset, related: related)
url = send("#{related.pluralize}_path")
respond_to do |format|
format.html { redirect_to(url) }
format.js { render plain: %(window.location.href = "#{url}";) }
format.json { render plain: flash[:warning], status: :not_found }
format.xml { render xml: [flash[:warning]], status: :not_found }
end
end
#----------------------------------------------------------------------------
def respond_to_access_denied
flash[:warning] = t(:msg_not_authorized, default: 'You are not authorized to take this action.')
respond_to do |format|
format.html { redirect_to(redirection_url) }
format.js { render plain: 'window.location.reload();' }
format.json { render plain: flash[:warning], status: :unauthorized }
format.xml { render xml: [flash[:warning]], status: :unauthorized }
end
end
#----------------------------------------------------------------------------
def redirection_url
# Try to redirect somewhere sensible. Note: not all controllers have an index action
if current_user.present?
respond_to?(:index) && action_name != 'index' ? { action: 'index' } : root_url
else
login_url
end
end
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token'
headers['Access-Control-Max-Age'] = "1728000"
end
def cors_preflight_check
if request.method == 'OPTIONS'
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Token'
headers['Access-Control-Max-Age'] = '1728000'
render plain: ''
end
end
def configure_devise_parameters
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit(:username, :email, :password, :password_confirmation)
end
end
def find_class(asset)
Rails.application.eager_load! unless Rails.application.config.cache_classes
classes = ActiveRecord::Base.descendants.map(&:name)
find = classes.find { |m| m == asset.classify }
if find
find.safe_constantize
else
raise "Unknown resource"
end
end
# In a number of places, we pass ?previous=(id) or ?previous=crm.find_form...
# This method centralises all of the places we can pass in a previous param
# and extracts an int ID, or nil
def detect_previous_id
return unless params[:previous]
return if params[:previous].start_with?("crm")
params[:previous].to_i
end
ActiveSupport.run_load_hooks(:fat_free_crm_application_controller, self)
end