Newer
Older
dmpopidor / app / controllers / org_admin / templates_controller.rb
# frozen_string_literal: true

module OrgAdmin

  class TemplatesController < ApplicationController

    include Paginable
    include Versionable
    after_action :verify_authorized

    # The root version of index which returns all templates
    # GET /org_admin/templates
    # -----------------------------------------------------
    def index
      authorize Template
      templates = Template.latest_version.where(customization_of: nil)
      published = templates.select { |t| t.published? || t.draft? }.length

      @orgs              = Org.all
      @title             = _("All Templates")
      @templates         = templates.includes(:org)
      @query_params      = { sort_field: "templates.title", sort_direction: "asc" }
      @all_count         = templates.length
      @published_count   = published.present? ? published : 0
      @unpublished_count = if published.present?
                             (templates.length - published)
                           else
                             templates.length
                           end
      render :index
    end

    # A version of index that displays only templates that belong to the user's org
    # GET /org_admin/templates/organisational
    # -----------------------------------------------------
    def organisational
      authorize Template
      templates = Template.latest_version_per_org(current_user.org.id)
                          .where(customization_of: nil, org_id: current_user.org.id)
      published = templates.select { |t| t.published? || t.draft? }.length

      @orgs  = current_user.can_super_admin? ? Org.all : nil
      @title = if current_user.can_super_admin?
                 _("%{org_name} Templates") % { org_name: current_user.org.name }
               else
                 _("Own Templates")
               end
      @templates         = templates
      @query_params      = { sort_field: "templates.title", sort_direction: "asc" }
      @all_count         = templates.length
      @published_count   = published.present? ? published : 0
      @unpublished_count = if published.present?
                             templates.length - published
                           else
                             templates.length
                           end
      render :index
    end

    # A version of index that displays only templates that are customizable
    # GET /org_admin/templates/customisable
    # -----------------------------------------------------
    def customisable
      authorize Template
      customizations = Template.latest_customized_version_per_org(current_user.org.id)
                               .where(org_id: current_user.org.id)
      funder_templates = Template.latest_customizable.includes(:org)
      # We use this to validate the counts below in the event that a template was
      # customized but the base template org is no longer a funder
      funder_template_families = funder_templates.collect(&:family_id)
      published = customizations.select { |t| t.published? || t.draft? }.length

      @orgs = current_user.can_super_admin? ? Org.all : []
      @title = _("Customizable Templates")
      @templates = funder_templates
      @customizations = customizations
      @query_params = { sort_field: "templates.title", sort_direction: "asc" }
      @all_count = funder_templates.length
      @published_count = published.present? ? published : 0
      @unpublished_count = if published.present?
                             (customizations.length - published)
                           else
                             customizations.length
                           end
      @not_customized_count = funder_templates.length - customizations.length

      render :index
    end

    # GET /org_admin/templates/[:id]
    def show
      template = Template.find(params[:id])
      authorize template
      # Load the info needed for the overview section if the authorization check passes!
      phases = template.phases
                       .includes(sections: { questions: :question_options })
                       .order("phases.number", "sections.number", "questions.number",
                              "question_options.number")
                       .select("phases.title", "phases.description", "sections.title",
                               "questions.text", "question_options.text")
      if !template.latest?
        # rubocop:disable Metrics/LineLength
        flash[:notice] = _("You are viewing a historical version of this template. You will not be able to make changes.")
        # rubocop:enable Metrics/LineLength
      end
      render "container", locals: {
        partial_path: "show",
        template: template,
        phases: phases,
        referrer: get_referrer(template, request.referrer) }
    end

    # GET /org_admin/templates/:id/edit
    def edit
      template = Template.includes(:org, :phases).find(params[:id])
      authorize template
      # Load the info needed for the overview section if the authorization check passes!
      phases = template.phases.includes(sections: { questions: :question_options }).
                        order("phases.number",
                              "sections.number",
                              "questions.number",
                              "question_options.number").
                        select("phases.title",
                               "phases.description",
                               "sections.title",
                               "questions.text",
                               "question_options.text")
      if !template.latest?
        redirect_to org_admin_template_path(id: template.id)
      else
        render "container", locals: {
          partial_path: "edit",
          template: template,
          phases: phases,
          referrer: get_referrer(template, request.referrer) }
      end
    end

    # GET /org_admin/templates/new
    def new
      authorize Template
      render "container", locals: {
        partial_path: "new",
        template: Template.new(org: current_user.org),
        referrer: request.referrer.present? ? request.referrer : org_admin_templates_path
      }
    end

    # POST /org_admin/templates
    def create
      authorize Template
      # creates a new template with version 0 and new family_id
      template = Template.new(template_params)
      template.org_id = current_user.org.id
      template.links = if params["template-links"].present?
                         ActiveSupport::JSON.decode(params["template-links"])
                       else
                         { "funder": [], "sample_plan": [] }
                       end
      if template.save
        redirect_to edit_org_admin_template_path(template),
                    notice: success_message(template_type(template), _("created"))
      else
        flash[:alert] = failed_create_error(template, template_type(template))
        render partial: "org_admin/templates/new",
               locals: { template: template, hash: hash }
      end
    end

    # PUT /org_admin/templates/:id (AJAXable)
    # -----------------------------------------------------
    def update
      template = Template.find(params[:id])
      authorize template
      begin
        template.assign_attributes(template_params)
        if params["template-links"].present?
          template.links = ActiveSupport::JSON.decode(params["template-links"])
        end
        if template.save
          render(status: :ok,
                 json: { msg: success_message(template_type(template), _("saved")) })
        else
          # Note failed_update_error may return HTML tags (e.g. <br/>) and therefore the
          # client should parse them accordingly
          render(status: :bad_request,
                 json: { msg: failed_update_error(template, template_type(template)) })
        end
      rescue ActiveSupport::JSON.parse_error
        render(status: :bad_request,
               json: { msg: _("Error parsing links for a #{template_type(template)}") })
        return
      rescue => e
        render(status: :forbidden, json: { msg: e.message }) and return
      end
    end

    # DELETE /org_admin/templates/:id
    def destroy
      template = Template.find(params[:id])
      authorize template
      versions = Template.includes(:plans).where(family_id: template.family_id)
      if versions.select { |t| t.plans.length > 0 }.empty?
        versions.each do |version|
          if version.destroy!
            flash[:notice] = success_message(template_type(template), _("removed"))
          else
            flash[:alert] = failed_destroy_error(template, template_type(template))
          end
        end
      else
        # rubocop:disable Metrics/LineLength
        flash[:alert] = _("You cannot delete a #{template_type(template)} that has been used to create plans.")
        # rubocop:enable Metrics/LineLength
      end
      if request.referrer.present?
        redirect_to request.referrer
      else
        redirect_to org_admin_templates_path
      end
    end

    # GET /org_admin/templates/:id/history
    def history
      template = Template.find(params[:id])
      authorize template
      templates = Template.where(family_id: template.family_id)
      local_referrer = if template.customization_of.present?
                         customisable_org_admin_templates_path
                       else
                         organisational_org_admin_templates_path
                       end
      render "history", locals: {
        templates: templates,
        query_params: { sort_field: "templates.version", sort_direction: "desc" },
        referrer: local_referrer,
        current: templates.maximum(:version)
      }
    end

    # POST /org_admin/templates/:id/customize
    def customize
      template = Template.find(params[:id])
      authorize template
      if template.customize?(current_user.org)
        begin
          customisation = template.customize!(current_user.org)
          redirect_to org_admin_template_path(customisation)
        rescue StandardError => e
          flash[:alert] = _("Unable to customize that template.")
          if request.referrer.present?
            redirect_to request.referrer
          else
            redirect_to org_admin_templates_path
          end
        end
      else
        flash[:notice] = _("That template is not customizable.")
        if request.referrer.present?
          redirect_to request.referrer
        else
          redirect_to org_admin_templates_path
        end
      end
    end

    # POST /org_admin/templates/:id/transfer_customization
    # the funder template's id is passed through here
    def transfer_customization
      template = Template.includes(:org).find(params[:id])
      authorize template
      if template.upgrade_customization?
        begin
          new_customization = template.upgrade_customization!
          redirect_to org_admin_template_path(new_customization)
        rescue StandardError => e
          flash[:alert] = _("Unable to transfer your customizations.")
          if request.referrer.present?
            redirect_to request.referrer
          else
            redirect_to org_admin_templates_path
          end
        end
      else
        flash[:notice] = _("That template is no longer customizable.")
        if request.referrer.present?
          redirect_to request.referrer
        else
          redirect_to org_admin_templates_path
        end
      end
    end

    # POST /org_admin/templates/:id/copy (AJAX)
    def copy
      template = Template.find(params[:id])
      authorize template
      begin
        new_copy = template.generate_copy!(current_user.org)
        flash[:notice] = "#{template_type(template).capitalize} was successfully copied."
        redirect_to edit_org_admin_template_path(new_copy)
      rescue StandardError => e
        flash[:alert] = failed_create_error(template, template_type(template))
        if request.referrer.present?
          redirect_to request.referrer
        else
          redirect_to org_admin_templates_path
        end
      end
    end

    # PATCH /org_admin/templates/:id/publish  (AJAX)
    def publish
      template = Template.find(params[:id])
      authorize template
      # rubocop:disable Metrics/LineLength
      if template.latest?
        # Now make the current version published
        if template.update_attributes!(published: true)
          flash[:notice] = _("Your #{template_type(template)} has been published and is now available to users.")
        else
          flash[:alert] = _("Unable to publish your #{template_type(template)}.")
        end
      else
        flash[:alert] = _("You can not publish a historical version of this #{template_type(template)}.")
      end
      # rubocop:enable Metrics/LineLength
      redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path
    end

    # PATCH /org_admin/templates/:id/unpublish  (AJAX)
    def unpublish
      template = Template.find(params[:id])
      authorize template
      versions = Template.where(family_id: template.family_id)
      versions.each do |version|
        unless version.update_attributes!(published: false)
          flash[:alert] = _("Unable to unpublish your #{template_type(template)}.")
        end
      end
      unless flash[:alert].present?
        flash[:notice] = _("Successfully unpublished your #{template_type(template)}")
      end
      redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path
    end

    # GET /org_admin/template_options  (AJAX)
    # Collect all of the templates available for the org+funder combination
    def template_options
      org_id = (plan_params[:org_id] == "-1" ? "" : plan_params[:org_id])
      funder_id = (plan_params[:funder_id] == "-1" ? "" : plan_params[:funder_id])
      authorize Template.new
      templates = []

      if org_id.present? || funder_id.present?
        unless funder_id.blank?
          # Load the funder's template(s) minus the default template (that gets swapped
          # in below if NO other templates are available)
          templates = Template.latest_customizable
                              .where(org_id: funder_id).select { |t| !t.is_default? }
          unless org_id.blank?
            # Swap out any organisational cusotmizations of a funder template
            templates = templates.map do |tmplt|
              customization = Template.published
                                      .latest_customized_version(tmplt.family_id,
                                                                 org_id).first
              # Only provide the customized version if its still up to date with the
              # funder template!
              if customization.present? && !customization.upgrade_customization?
                customization
              else
                tmplt
              end
            end
          end
        end

        # If the no funder was specified OR the funder matches the org
        if funder_id.blank? || funder_id == org_id
          # Retrieve the Org's templates
          templates << Template.published
                               .organisationally_visible
                               .where(org_id: org_id, customization_of: nil).to_a
        end
        templates = templates.flatten.uniq
      end

      # If no templates were available use the default template
      if templates.empty?
        default = Template.default
        if default.present?
          customization = Template.published.latest_customized_version(default.family_id,
                                                                       org_id).first
          templates << (customization.present? ? customization : default)
        end
      end
      render json: {
        templates: templates.sort(&:title).collect { |t| { id: t.id, title: t.title } }
      }
    end

    private

    def plan_params
      params.require(:plan).permit(:org_id, :funder_id)
    end

    def template_params
      params.require(:template).permit(:title, :description, :visibility, :links)
    end

    def template_type(template)
      template.customization_of.present? ? _("customisation") : _("template")
    end

    def get_referrer(template, referrer)
      return org_admin_templates_path unless referrer.present?
      if referrer.end_with?(new_org_admin_template_path) ||
           referrer.end_with?(edit_org_admin_template_path) ||
           referrer.end_with?(org_admin_template_path)

        if template.customization_of.present?
          customisable_org_admin_templates_path
        else
          organisational_org_admin_templates_path
        end
      else
        request.referrer
      end
    end

  end

end