oneclickorgs/one-click-orgs

View on GitHub
app/controllers/application_controller.rb

Summary

Maintainability
C
7 hrs
Test Coverage
require 'lib/one_click_orgs/setup'
require 'lib/unauthenticated'
require 'lib/not_found'
require 'lib/one_click_orgs/organisation_resolver'

class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :ensure_set_up
  before_filter :ensure_organisation_exists
  before_filter :ensure_authenticated
  before_filter :ensure_member_active_or_pending
  before_filter :ensure_member_inducted
  before_filter :prepare_notifications
  before_filter :load_analytics_events_from_session

  # CURRENT ORGANISATION

  # Returns the organisation corresponding to the subdomain that the current
  # request has been made on (or just returns the organisation if the app
  # is running in single organisation mode).
  def current_organisation
    unless @current_organisation
      if Setting[:single_organisation_mode]
        @current_organisation = Organisation.first
      else
        @current_organisation = Organisation.find_by_host(
          request.host_with_port
        )
      end
      install_organisation_resolver(@current_organisation)
    end
    @current_organisation
  end
  alias :co :current_organisation

  # USER LOGIN

  def current_user
    @current_user if user_logged_in?
  end

  def log_in(user)
    self.current_user = user
    current_user.update_attribute(:last_logged_in_at, Time.now.utc)
  end

  # Returns true if a user is logged in; false otherwise.
  def user_logged_in?
    current_user = @current_user
    current_user ||= session[:user] && co ? co.members.find_by_id(session[:user]) : false
    @current_user = current_user
    current_user.is_a?(Member)
  end

  # Stores the given user as the 'current user', thus marking them as logged in.
  def current_user=(user)
    session[:user] = (user.nil? || user.is_a?(Symbol)) ? nil : user.id
    @current_user = user
  end

  # ADMINISTRATOR LOGIN

  def current_administrator
    @current_administrator if administrator_logged_in?
  end

  def log_in_administrator(administrator)
    self.current_administrator = administrator
  end

  # Returns true if an administrator is logged in; false otherwise.
  def administrator_logged_in?
    current_administrator = @current_administrator
    current_administrator ||= session[:administrator] && Administrator.find_by_id(session[:administrator])
    @current_administrator = current_administrator
    current_administrator.is_a?(Administrator)
  end

  # Stores the given administrator as the 'current administrator', thus marking them as logged in.
  def current_administrator=(administrator)
    session[:administrator] = (administrator.nil? || administrator.is_a?(Symbol)) ? nil : administrator.id
    @current_administrator = administrator
  end

  def store_location
    session[:return_to] = request.fullpath
  end

  def redirect_back_or_default(default = root_path)
    session[:return_to] ? redirect_to(session[:return_to]) : redirect_to(default)
    session[:return_to] = nil
  end

  # MAKING PDFS

  def generate_pdf(filename='Download', options={})
    options = {
      :header_right => "Printed on #{Time.now.utc.to_s(:long_date)}",
      :header_line => true
    }.with_indifferent_access.merge(options)

    organisation = options[:organisation] || co
    @organisation_name = organisation.name

    # If wkhtmltopdf is working...
    begin
      html = options[:html] || render_to_string(:layout => false)

      # Call PDFKit with any wkhtmltopdf --extended-help options
      kit = PDFKit.new(html, :page_size => 'A4',
        :header_right => options[:header_right],
        :header_left => options[:header_left],
        :footer_right => '[page]',
        :header_font_size => 8,
        :footer_font_size => 11,
        :header_line => options[:header_line],
        :header_spacing => 9,
        :footer_spacing => 9
      )

      if options[:stylesheet]
        kit.stylesheets << options[:stylesheet]
      end
      kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/pdf.css"

      send_data(kit.to_pdf, :filename => "#{@organisation_name} #{filename}.pdf",
        :type => 'application/pdf', :disposition => 'attachment')
        # disposition: choose between attachment/inline to force download

      return

    # Fail if it's not installed
    rescue PDFKit::NoExecutableError
      flash[:error] = "The software for generating PDFs (wkhtmltopdf) isn't installed. \
        Contact the maintainer of your One Click Orgs installation for help."
      redirect_to :back

    end
  end

  # NOTIFICATIONS

  def prepare_notifications
    return unless current_user

    # If you have a notification you want to show the user, put the
    # logic in here, and the template in shared/notifications.
    #
    # Call show_notification_once if you only want the user to
    # see your notification once ever (e.g. a 'welcome to the
    # system' notification).
    #
    # Call show_notification if it doesn't matter whether the user
    # has seen this notification before (e.g. a 'you have a new
    # message' notification).

    if co.is_a?(Association)
      if co.pending? && current_user.member_class.name == "Founder"
        show_notification_once(:convener_welcome)
      end

      fap = co.found_association_proposals.last
      # if the organisation is pending
      # and the voting is finished (fap.closed)
      # and a founding proposal exists
      # and is the proposal
      if co.pending? && fap && fap.closed? && !fap.accepted?
        show_notification_once(:founding_proposal_failed, fap.close_date)
      end

      # Only display founding_proposal_passed notification to
      # members who were founding members
      if co.active? && fap && current_user.created_at <= fap.creation_date
        show_notification_once(:founding_proposal_passed)
      end
    end
  end

  # Show a one-off notification to a user, to notify of
  # a specific event happening.
  #
  # With #show_notification_once, a notification of a given type (e.g. a 'convener_welcome' notification) will only be shown once
  # to a given user. If you want to show the notification regardless of whether the user has already seen it before,
  # use #show_notification instead.
  #
  # As an exception to this, you can pass the ignore_earlier_than parameter. If this user has seen this type of notification
  # earlier than the timestamp you pass, the notification will be shown again.
  #
  # @param [Symbol] notification the notification type, `:founding_proposal_passed` or `:founding_proposal_failed`
  # @param [optional, Timestamp] ignore seen-notifications earlier than this time
  # @return [String] notification the string relating to the kind of notification `founding_proposal_passed`.
  #
  # @example Show a notification a user once that their proposal failed, allowing us to render the partial 'founding_proposal_failed' in the view
  #   show_notification_once(:founding_proposal_failed)
  #
  def show_notification_once(notification, ignore_earlier_than = nil)
    return unless current_user
    return if current_user.has_seen_notification?(notification, ignore_earlier_than)
    show_notification(notification)
  end

  def show_notification(notification)
    @notification = notification
  end

  # ANALYTICS

  def track_analytics_event(event_name, options={})
    return unless Rails.env.production? && OneClickOrgs::GoogleAnalytics.active?
    if options[:now]
      @analytics_events ||= []
      @analytics_events.push(event_name)
    else
      session[:analytics_events] ||= []
      session[:analytics_events].push(event_name)
    end
  end

  def load_analytics_events_from_session
    return unless Rails.env.production? && OneClickOrgs::GoogleAnalytics.active?
    unless session[:analytics_events].blank?
      @analytics_events ||= []
      session[:analytics_events].dup.each do |event|
        @analytics_events.push(event)
        session[:analytics_events].delete(event)
      end
    end
  end

protected

  # BEFORE FILTER DEFINITIONS

  def ensure_set_up
    unless OneClickOrgs::Setup.complete?
      redirect_to(:controller => 'setup')
    end
  end

  def ensure_organisation_exists
    unless current_organisation
      redirect_to(new_organisation_url(host_and_port(Setting[:signup_domain])))
    end
  end

  def ensure_authenticated
    if user_logged_in?
      true
    else
      raise Unauthenticated
    end
  end

  def ensure_member_active_or_pending
    if current_user && current_user.inactive?
      session[:user] = nil
      raise Unauthenticated
    end
  end

  def ensure_member_inducted
    case co
    when Association
      redirect_to_welcome_member if co.active? && current_user && !current_user.inducted?
    end
  end

  def ensure_administrator_authenticated
    if administrator_logged_in?
      true
    else
      raise UnauthenticatedAdministrator
    end
  end

  def ensure_administration_subdomain
    if request.host_with_port == Setting[:signup_domain]
      true
    else
      redirect_to(new_organisation_url(host_and_port(Setting[:signup_domain])))
    end
  end

  def redirect_to_welcome_member
    redirect_to(:controller => 'welcome', :action => 'index')
  end

  def find_constitution
    @constitution = co.constitution
  end

  def host_and_port(domain)
    host, port = domain.split(':')
    if port
      {:host => host, :port => port}
    else
      {:host => host}
    end
  end

  # EXCEPTION HANDLING

  rescue_from NotFound, :with => :render_404
  rescue_from ActiveRecord::RecordNotFound, :with => :render_404
  def render_404
    render :file => "#{Rails.root}/public/404", :status => 404, :layout => false
  end

  rescue_from Unauthenticated, :with => :handle_unauthenticated
  def handle_unauthenticated
    store_location
    redirect_to login_path
  end

  rescue_from UnauthenticatedAdministrator, :with => :handle_unauthenticated_administrator
  def handle_unauthenticated_administrator
    store_location
    redirect_to admin_login_path
  end

  rescue_from CanCan::AccessDenied do |exception|
    flash[:error] = exception.message
    redirect_to root_path
  end

  # ORGANISATION RESOLVER

  # Configures the controller's view context to use an
  # OrganisationResolver based on the given organisation's class when
  # resolving (looking up) template paths.
  def install_organisation_resolver(organisation)
    view_paths.dup.each do |view_path|
      prepend_view_path(
        OneClickOrgs::OrganisationResolver.new(
          view_path.to_path,
          organisation.class
        )
      )
    end
  end
end