app/controllers/open_conference_ware/application_controller.rb
module OpenConferenceWare
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
protect_from_forgery # secret: '56b4f0ad244d35b7e0d30ba0c5e1ae61'
# Provide methods for checking settings succinctly
include SettingsCheckersMixin
# Provide faux routes, e.g., #tracks_path
include FauxRoutesMixin
# Provide access to page_title in controllers
include PageTitleHelper
# Setup breadcrumbs
include BreadcrumbsMixin
add_breadcrumbs(OpenConferenceWare.breadcrumbs)
# Filters
before_filter :assign_events
before_filter :assign_current_event_without_redirecting
before_filter :log_the_current_user
before_filter :log_the_session
rescue_from ActionController::UnknownFormat do |e|
render(text: 'Not Found', status: 404)
end
#---[ Authentication ]--------------------------------------------------
# Store the given user in the session.
def current_user=(new_user)
session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
@current_user = new_user
end
# Accesses the current user from the session.
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
rescue ActiveRecord::RecordNotFound
reset_session
end
helper_method :current_user
# Returns true or false if the user is logged in.
# Preloads @current_user with the user model if they're logged in.
def logged_in?
!!current_user
end
helper_method :logged_in?
# Filter method to enforce a login requirement.
def authentication_required
logged_in? || access_denied(message: "Please sign in to access the requested page.")
end
# Redirect as appropriate when an access request fails.
def access_denied(opts={})
message = opts[:message] || "Access denied, please sign in with enough privileges to complete that operation."
fallback_url = opts[:fallback_url] || opts[:fallback] || sign_in_path
store_location
redirect_to fallback_url, alert: message
end
# Store the URI of the current request in the session.
#
# We can return to this location by calling #redirect_back_or_default.
def store_location(path=nil)
session[:return_to] = path || request.fullpath
end
# Redirect to the URI stored by the most recent store_location call or
# to the passed default.
def redirect_back_or_default(default=nil)
redirect_to(session[:return_to] || default || default_path)
session[:return_to] = nil
end
alias_method :redirect_back_or_to, :redirect_back_or_default
def default_path
if @event
if @event.proposal_status_published?
event_sessions_path(@event)
else
event_proposals_path(@event)
end
else
proposals_path
end
end
protected
#---[ General ]---------------------------------------------------------
# Return the current User record or a nil if not logged in.
def current_user_or_nil
return(current_user.kind_of?(User) ? current_user : nil)
end
helper_method :current_user_or_nil
# Return the current_user's email address, from either the currently-logged
# in user or the cookie, else nil.
def current_email
return(current_user_or_nil.try(:email) || session[:email])
end
helper_method :current_email
# Return a cache key for the currently authenticated or anonymous user.
def current_user_cache_key
return current_user_or_nil.try(:id) || -1
end
helper_method :current_user_cache_key
# Return a cache key for the current event.
def current_event_cache_key
return @event.try(:id) || -1
end
helper_method :current_event_cache_key
# Are we running in a development mode?
def development_mode?
return %w[development preview].include?(Rails.env)
end
helper_method :development_mode?
def event_schedule?
proposal_start_times? && proposal_statuses? && event_rooms?
end
helper_method :event_schedule?
def schedule_visible?
(@event.schedule_published? || admin?) && event_schedule?
end
helper_method :schedule_visible?
# Flash notification levels allowed by #notify.
NOTIFY_LEVELS = Set.new([:notice, :success, :failure])
# Sets or appends the flash notification.
#
# Arguments:
# * level: Symbol name of the notificaiton level, e.g. "failure".
# * message: String message to display.
def notify(level, message)
level = level.to_sym
raise ArgumentError, "Invalid flash notification level: #{level}" unless NOTIFY_LEVELS.include?(level)
flash[level] = "#{flash[level]} #{message}".strip.html_safe
end
#---[ Access control ]--------------------------------------------------
# Can the current user edit the current +record+?
def can_edit?(record)
raise ArgumentError, "No record specified" unless record
if logged_in?
if current_user.admin?
true
else
# Normal user
case record
when Proposal
# FIXME Add setting to determine if users can alter their proposals after the accepting_proposals deadline passed.
### accepting_proposals?(record) && record.can_alter?(current_user)
record.can_alter?(current_user)
when User
current_user == record
else
raise TypeError, "Unknown record type: #{record.class}"
end
end
else
false
end
end
helper_method :can_edit?
# Is the current user an admin?
def admin?
logged_in? && current_user.admin?
end
helper_method :admin?
def current_role
(logged_in? && current_user.role) || :default
end
helper_method :current_role
# Ensure user is an admin, or bounce them to the admin prompt.
def require_admin
admin? || access_denied(message: "You must have administrator privileges to access the requested page.")
end
def current_user_is_proposal_speaker?
if logged_in?
return @proposal.users.include?(current_user)
end
return false
end
helper_method :current_user_is_proposal_speaker?
# Is this event accepting proposals?
def accepting_proposals?(record=nil)
event = \
case record
when Event then record
when Proposal then record.event
else @event
end
return event.try(:accepting_proposals?)
end
helper_method :accepting_proposals?
def selector?
logged_in? && current_user.selector?
end
helper_method :selector?
def require_selector
selector? || access_denied(message: "You must be part of the selection committee to access the requested page.")
end
#---[ Logging ]---------------------------------------------------------
def log_the_current_user
Rails.logger.info("User: #{current_user.id}, #{current_user.label}") if current_user_or_nil
end
def log_the_session
Rails.logger.info("Session: #{session.to_hash.inspect}") if session.respond_to?(:data)
end
#---[ Assign items ]----------------------------------------------------
# Assign an @events variable for use by the layout when displaying available events.
def assign_events
@events = Event.all
end
# Return the event and a status which describes how the event was assigned. The status can be one of the following:
# * :assigned_to_param
# * :invalid_param
# * :invalid_proposal_event
# * :assigned_to_current
# * :empty
def get_current_event_and_assignment_status
invalid = false
# Try finding event using params:
event_id_key = controller_name == "events" ? :id : :event_id
if key = params[event_id_key]
if event = Event.find_by_slug(key)
return [event, :assigned_to_param]
else
logger.info "error, couldn't find event from key: #{key}"
invalid = :invalid_param
end
end
# Try finding event using proposal:
if controller_name == "proposals" && params[:id]
if proposal = Proposal.find_by_id(params[:id])
if proposal.event
return [proposal.event, :assigned_to_param]
else
logger.info "error, couldn't find event from Proposal ##{proposal.id}"
invalid = :invalid_proposal_event
end
end
end
# Try finding the current event.
if event = Event.current
logger.info "assigned to current event"
if invalid
return [event, invalid]
else
return [event, :assigned_to_current]
end
end
logger.info "error, no current event found"
return [nil, :empty]
end
# Assign @event if it's not already set. Also set the
# @event_assignment value to describe how the @event was assigned,
# which can be one of the following values:
# * :assigned_already
# * Or any of the statuses described in
# #get_current_event_and_assignment_status
def assign_current_event_without_redirecting
invalid_param = false
# Only assign event if one isn't already assigned.
if @event
logger.info "already assigned"
@event_assignment = :assigned_already
else
@event, @event_assignment = get_current_event_and_assignment_status()
end
return false
end
# Ensure that @event is assigned (by #assign_current_event_without_redirecting).
# If not, display an error or force the admin to create a new event.
def assert_current_event_or_redirect
case @event_assignment
when :invalid_proposal_event
flash[:failure] = "Invalid proposal has no event, redirecting to current event's proposals."
flash.keep
return redirect_to(event_proposals_path(@event))
when :invalid_param
flash[:failure] = "Couldn't find event, redirected to current event."
flash.keep
return redirect_to(event_path(@event))
when :empty
flash[:failure] = "No current event available. Admin needs to create one."
if admin?
# Allow admin to create an event.
flash.keep
return redirect_to(manage_events_path)
else
# Display a static error page.
render template: 'open_conference_ware/events/index'
return true # Cancel further processing
end
else
return false
end
end
# Redirect the user to the canonical event path if they're visiting a path that doesn't start with '/events'.
def normalize_event_path_or_redirect
# When running under a prefix (e.g., "thin --prefix /omg start"), this value will be set to "/omg", else "".
if request.format.to_sym == :html
if request.path.match(%r{^#{OpenConferenceWare.mounted_path("/events")}})
return false
else
if controller_name == "proposals" && action_name == "sessions_index"
path = event_sessions_path(@event)
elsif controller_name == "proposals" && action_name == "schedule"
path = event_schedule_path(@event)
else
path = OpenConferenceWare.mounted_path("/events/#{@event.to_param}/#{controller_name}#{action_name == 'index' ? '' : "/#{action_name}" }")
end
flash.keep
return redirect_to(path)
end
else
# Non-HTTP requests don't understand redirects, so leave these alone
return false
end
end
# Ensure that the proposal status is defined, else redirect back to proposals
def assert_proposal_status_published
display = false
if @event.proposal_status_published?
display = true
else
if admin?
display = true
flash[:notice] = "Session information has not yet been published, only admins can see this page."
end
end
unless display
flash[:failure] = "Session information has not yet been published for this event."
return redirect_to((params[:id] && request.path.include?("session")) ? proposal_path(params[:id]) : event_proposals_path(@event))
end
end
# Ensure that the schedule is published
def assert_schedule_published
display = admin? || schedule_visible?
flash[:notice] = "The schedule has not yet been published, only admins can see this page." if admin? && !schedule_visible?
unless display
flash[:failure] = "The schedule has not yet been published for this event."
return redirect_to(@event.proposal_status_published? ? event_sessions_path(@event) : event_proposals_path(@event))
end
end
# Sets @user based on params[:id] and adds related breadcrumbs.
def assert_user
case self
when UsersController
user_id = params[:id]
when UserFavoritesController
user_id = params[:user_id]
else
raise TypeError
end
if user_id == "me"
if logged_in?
@user = current_user
else
return access_denied(message: "Please sign in to access your user profile.")
end
else
begin
@user = User.find(user_id)
rescue ActiveRecord::RecordNotFound
flash[:failure] = "User not found or deleted"
return redirect_to(users_path)
end
end
# TODO Move breadcrumbs to filters/actions that rely on user.
add_breadcrumb "Users", users_path
add_breadcrumb @user.label, user_path(@user)
add_breadcrumb "Edit" if ["edit", "update"].include?(action_name)
end
# Assert that #current_user can edit record.
def assert_record_ownership
case self
when ProposalsController
record = @proposal
when UsersController, UserFavoritesController
record = @user
failure_message = "Sorry, you can't edit other users."
else
raise TypeError
end
if admin?
return false # admin can always edit
else
if can_edit?(record)
return false # current_user can edit
else
flash[:failure] = failure_message ||= "Sorry, you can't edit #{record.class.name.pluralize.downcase} that aren't yours."
return redirect_to(record)
end
end
end
# OMFG HORRORS!!1!
def assign_prefetched_hashes
@users = Defer { @event.users }
@users_hash = Defer { Hash[@users.map{|t| [t.id, t]}] }
@speakers = Defer { @event.speakers }
@speakers_hash = Defer { Hash[@speakers.map{|t| [t.id, t]}] }
@tracks_hash = Defer { Hash[@event.tracks.order("title ASC").map{|t| [t.id, t]}] }
@rooms_hash = Defer { Hash[@event.rooms.map{|t| [t.id, t]}] }
@session_types_hash = Defer { Hash[@event.session_types.map{|t| [t.id, t]}] }
@proposals_hash = Defer { Hash[@event.proposals.order("submitted_at DESC").includes(:track, :session_type).map{|t| [t.id, t]}] }
@sessions_hash = Defer { Hash[@event.proposals.confirmed.order("submitted_at DESC").includes(:track, :session_type).map{|t| [t.id, t]}] }
@users_and_proposals = Defer { ActiveRecord::Base.connection.select_all(%{select open_conference_ware_proposals_users.user_id, open_conference_ware_proposals_users.proposal_id from open_conference_ware_proposals_users, open_conference_ware_proposals where open_conference_ware_proposals_users.proposal_id = open_conference_ware_proposals.id and open_conference_ware_proposals.event_id = #{@event.id}}) }
@users_and_sessions = Defer { ActiveRecord::Base.connection.select_all(%{select open_conference_ware_proposals_users.user_id, open_conference_ware_proposals_users.proposal_id from open_conference_ware_proposals_users, open_conference_ware_proposals where open_conference_ware_proposals_users.proposal_id = open_conference_ware_proposals.id and open_conference_ware_proposals.status = 'confirmed' and open_conference_ware_proposals.event_id = #{@event.id}}) }
@users_for_proposal_hash = Defer { @users_and_proposals.inject({}){|s,v| (s[v["proposal_id"].to_i] ||= Set.new) << @users_hash[v["user_id"].to_i]; s} }
@sessions_for_user_hash = Defer { @users_and_sessions.inject({}){|s,v| (s[v["user_id"].to_i] ||= Set.new) << @sessions_hash[v["proposal_id"].to_i]; s} }
@proposals_for_user_hash = Defer { @users_and_proposals.inject({}){|s,v| (s[v["user_id"].to_i] ||= Set.new) << @proposals_hash[v["proposal_id"].to_i]; s} }
@user_favorites_count_for_user_hash = Defer { ActiveRecord::Base.connection.select_all("select user_id, count(proposal_id) as favorites from open_conference_ware_user_favorites group by user_id").inject({}){|s,v| s[v["user_id"].to_i] = v["favorites"].to_i; s} }
end
# Warn admin to create event's session type and track if needed.
def warn_about_incomplete_event
if @event
if event_tracks? && @event.tracks.size == 0
if admin?
notify :notice, "This event needs a track, you should #{view_context.link_to 'create one', new_event_track_path(@event)}.".html_safe
else
notify :failure, "This event has no tracks, an admin must create at least one."
end
end
if event_session_types? && @event.session_types.size == 0
if admin?
notify :notice, "This event needs a session type, you should #{view_context.link_to 'create one', new_event_session_type_path(@event)}.".html_safe
else
notify :failure, "This event has no session types, an admin must create at least one."
end
end
end
end
end
end