Newer
Older
dmpopidor / app / controllers / plans_controller.rb
class PlansController < ApplicationController
  require 'pp'
  helper SettingsTemplateHelper

  after_action :verify_authorized, except: ['public_index', 'public_export']

  def index
    authorize Plan
    @plans = current_user.plans
  end

  # GET /plans/public_index
  # ------------------------------------------------------------------------------------
  def public_index
    @plans = Plan.publicly_visible
  end

  # GET /plans/new
  # ------------------------------------------------------------------------------------
  def new
    @plan = Plan.new
    authorize @plan

    # Get all of the available funders and non-funder orgs
    @funders = Org.funders.joins(:templates).where(templates: {published: true}).uniq.sort{|x,y| x.name <=> y.name }
    @orgs = (Org.institutions + Org.managing_orgs).flatten.uniq.sort{|x,y| x.name <=> y.name }

    # Get the current user's org
    @default_org = current_user.org if @orgs.include?(current_user.org)

    flash[:notice] = "#{_('This is a')} <strong>#{_('test plan')}</strong>" if params[:test]
    @is_test = params[:test] ||= false
    respond_to :html
  end

  # POST /plans
  # -------------------------------------------------------------------
  def create
    @plan = Plan.new
    authorize @plan
    
    @plan.principal_investigator = current_user.surname.blank? ? nil : "#{current_user.firstname} #{current_user.surname}"
    @plan.principal_investigator_email = current_user.email
    
    orcid = current_user.identifier_for(IdentifierScheme.find_by(name: 'orcid'))
    @plan.principal_investigator_identifier = orcid.identifier unless orcid.nil?
    
    @plan.funder_name = plan_params[:funder_name]

    @plan.visibility = (plan_params['visibility'].blank? ? Rails.application.config.default_plan_visibility :
                                                           plan_params[:visibility])

    # If a template hasn't been identified look for the available templates
    if plan_params[:template_id].blank?
      template_options(plan_params[:org_id], plan_params[:funder_id])

      # Return the 'Select a template' section
      respond_to do |format|
        format.js {}
      end

    # Otherwise create the plan
    else
      @plan.template = Template.find(plan_params[:template_id])

      if plan_params[:title].blank?
        @plan.title = current_user.firstname.blank? ? _('My Plan') + '(' + @plan.template.title + ')' :
                                    current_user.firstname + "'s" + _(" Plan")
      else
        @plan.title = plan_params[:title]
      end

      if @plan.save
        @plan.assign_creator(current_user)

        # pre-select org's guidance
        ggs = GuidanceGroup.where(org_id: plan_params[:org_id],
                                                     optional_subset: false,
                                                     published: true)
        if !ggs.blank? then @plan.guidance_groups << ggs end

        default = Template.find_by(is_default: true)

        msg = success_message(_('plan'), _('created'))

        if !default.nil? && default == @plan.template
          # We used the generic/default template
          msg += _('This plan is based on the default template.')

        elsif !@plan.template.customization_of.nil?
          # We used a customized version of the the funder template
          msg += "#{_('This plan is based on the')} #{plan_params[:funder_name]} #{_('template with customisations by the')} #{plan_params[:org_name]}"

        else
          # We used the specified org's or funder's template
          msg += "#{_('This plan is based on the')} #{@plan.template.org.name} template."
        end

        flash[:notice] = msg

        respond_to do |format|
          format.js { render js: "window.location='#{plan_url(@plan)}?editing=true'" }
        end

      else
        # Something went wrong so report the issue to the user
        flash[:alert] = failed_create_error(@plan, 'Plan')
        respond_to do |format|
          format.js {}
        end
      end
    end
  end



  # GET /plans/show
  def show
    @plan = Plan.eager_load(params[:id])
    authorize @plan

    @visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility

    @editing = (!params[:editing].nil? && @plan.administerable_by?(current_user.id))

    # Get all Guidance Groups applicable for the plan and group them by org
    @all_guidance_groups = @plan.get_guidance_group_options
    @all_ggs_grouped_by_org = @all_guidance_groups.sort.group_by(&:org)

    # Important ones come first on the page - we grab the user's org's GGs and "Organisation" org type GGs
    @important_ggs = []
    @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)]
    @all_ggs_grouped_by_org.each do |org, ggs|
      if org.organisation?
        @important_ggs << [org,ggs]
        @all_ggs_grouped_by_org.delete(org)
      end
    end

    # Sort the rest by org name for the accordion
    @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name}
    @selected_guidance_groups = @plan.guidance_groups.pluck(:id)
    
    @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(dmptemplate: @plan.template.customization_of).first)

    respond_to :html
  end


  # we can go into this with the user able to edit or not able to edit
  # the same edit form gets rendered but then different partials get used
  # to render the answers depending on whether it is readonly or not
  #
  # we may or may not have a phase param.
  # if we have none then we are editing/displaying the plan details
  # if we have a phase then we are editing that phase.
  #
  # GET /plans/1/edit
  def edit
    @plan = Plan.find(params[:id])
    authorize @plan
    
    @visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility
    
    # If there was no phase specified use the template's 1st phase
    @phase = (params[:phase].nil? ? @plan.template.phases.first : Phase.find(params[:phase]))
    @show_phase_tab = params[:phase]
    @readonly = !@plan.editable_by?(current_user.id)
    
    # Get all Guidance Groups applicable for the plan and group them by org
    @all_guidance_groups = @plan.get_guidance_group_options
    @all_ggs_grouped_by_org = @all_guidance_groups.sort.group_by(&:org)

    # Important ones come first on the page - we grab the user's org's GGs and "Organisation" org type GGs
    @important_ggs = []
    @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)]
    @all_ggs_grouped_by_org.each do |org, ggs|
      if org.organisation?
        @important_ggs << [org,ggs]
        @all_ggs_grouped_by_org.delete(org)
      end
    end

    # Sort the rest by org name for the accordion
    @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name}

    @selected_guidance_groups = @plan.guidance_groups.pluck(:id)
    @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(dmptemplate: @plan.template.customization_of).first)

    flash[:notice] = "#{_('This is a')} <strong>#{_('test plan')}</strong>" if params[:test]
    @is_test = params[:test] ||= false
    
    respond_to :html
  end


  # PUT /plans/1
  # PUT /plans/1.json
  def update
    @plan = Plan.find(params[:id])
    authorize @plan
    attrs = plan_params

    respond_to do |format|
      if @plan.update_attributes(attrs)
        format.html { redirect_to @plan, :editing => false, notice: success_message(_('plan'), _('saved')) }
        format.json { head :no_content }
      else
        flash[:alert] = failed_update_error(@plan, _('plan'))
        format.html { render action: "edit" }
      end
    end
  end



  def update_guidance_choices
    @plan = Plan.find(params[:id])
    authorize @plan
    guidance_group_ids = params[:guidance_group_ids].blank? ? [] : params[:guidance_group_ids].map(&:to_i)
    all_guidance_groups = @plan.get_guidance_group_options
    plan_groups = @plan.guidance_groups
    guidance_groups = GuidanceGroup.where( id: guidance_group_ids)
    all_guidance_groups.each do |group|
      # case where plan group exists but not in selection
      if plan_groups.include?(group) && ! guidance_groups.include?(group)
      #   remove from plan groups
        @plan.guidance_groups.delete(group)
      end
      #  case where plan group dosent exist and in selection
      if !plan_groups.include?(group) && guidance_groups.include?(group)
      #   add to plan groups
        @plan.guidance_groups << group
      end
    end
    @plan.save
    flash[:notice] = success_message(_('guidance choices'), _('saved'))
    redirect_to action: "show"
  end

  def share
    @plan = Plan.find(params[:id])
    authorize @plan
    #@plan_data = @plan.to_hash
  end


  def destroy
    @plan = Plan.find(params[:id])
    authorize @plan
    if @plan.destroy
      respond_to do |format|
        format.html { redirect_to plans_url, notice: success_message(_('plan'), _('deleted')) }
      end
    else
      respond_to do |format|
        flash[:alert] = failed_create_error(@plan, _('plan'))
        format.html { render action: "edit" }
      end
    end
  end

  # GET /status/1.json
  # only returns json, why is this here?
  def status
    @plan = Plan.find(params[:id])
    authorize @plan
    respond_to do |format|
      format.json { render json: @plan.status }
    end
  end

  def answer
    @plan = Plan.find(params[:id])
    authorize @plan
    if !params[:q_id].nil?
      respond_to do |format|
        format.json { render json: @plan.answer(params[:q_id], false).to_json(:include => :options) }
      end
    else
      respond_to do |format|
        format.json { render json: {} }
      end
    end
  end

  def show_export
    @plan = Plan.find(params[:id])
    authorize @plan
    render 'show_export'
  end



  def export
    @plan = Plan.find(params[:id])
    authorize @plan

    # If no format is specified, default to PDF
    params[:format] = 'pdf' if params[:format].nil?

    @exported_plan = ExportedPlan.new.tap do |ep|
      ep.plan = @plan
      ep.phase_id = params[:phase_id]
      ep.user = current_user
      ep.format = params[:format].to_sym
      plan_settings = @plan.settings(:export)

      Settings::Template::DEFAULT_SETTINGS.each do |key, value|
        ep.settings(:export).send("#{key}=", plan_settings.send(key))
      end
    end

    begin
      @exported_plan.save!
      file_name = @exported_plan.settings(:export)[:value]['title'].gsub(/ /, "_")

      respond_to do |format|
        format.html
        format.csv  { send_data @exported_plan.as_csv,  filename: "#{file_name}.csv" }
        format.text { send_data @exported_plan.as_txt,  filename: "#{file_name}.txt" }
        format.docx { render docx: 'export', filename: "#{file_name}.docx" }
        format.pdf do
          @formatting = @plan.settings(:export).formatting
          render pdf: file_name,
            margin: @formatting[:margin],
            footer: {
              center:    _('This document was generated by %{application_name}') % {application_name: Rails.configuration.branding[:application][:name]},
              font_size: 8,
              spacing:   (@formatting[:margin][:bottom] / 2) - 4,
              right:     '[page] of [topage]'
            }
        end
      end
    rescue ActiveRecord::RecordInvalid => e
      redirect_to show_export_plan_path(@plan), alert: _('%{format} is not a valid exporting format. Available formats to export are %{available_formats}.') %
      {format: params[:format], available_formats: ExportedPlan::VALID_FORMATS.to_s}
    end
  end

  # GET /plans/[:plan_slug]/public_export
  # -------------------------------------------------------------
  def public_export
    @plan = Plan.find(params[:id])
    
    # If the plan has multiple phases we should export each
    @plan.phases.each do |phase|
      @exported_plan = ExportedPlan.new.tap do |ep|
        ep.plan = @plan
        ep.phase_id = phase.id
        ep.format = :pdf
        plan_settings = @plan.settings(:export)

        Settings::Template::DEFAULT_SETTINGS.each do |key, value|
          ep.settings(:export).send("#{key}=", plan_settings.send(key))
        end
      end
      
      begin
        @exported_plan.save!
        # If the template has multiple phases then append the phase title to the filename
        if @plan.phases.count > 1
          file_name = "#{@plan.title.gsub(/ /, "_")}-#{phase.title}"
        else
          file_name = @plan.title.gsub(/ /, "_")
        end
        
        respond_to do |format|
          format.pdf do
            @formatting = @plan.settings(:export).formatting
            render pdf: file_name,
              margin: @formatting[:margin],
              footer: {
                center:    _('This document was generated by %{application_name}') % {application_name: Rails.configuration.branding[:application][:name]},
                font_size: 8,
                spacing:   (@formatting[:margin][:bottom] / 2) - 4,
                right:     '[page] of [topage]'
              }
          end
        end
      rescue ActiveRecord::RecordInvalid => e
        redirect_to show_export_plan_path(@plan), alert: _('Unable to download the DMP at this time.')
      end
    end
  end
  
  def duplicate
    plan = Plan.find(params[:id])
    authorize plan
    @plan = Plan.deep_copy(plan)
    respond_to do |format|
      if @plan.save
        @plan.assign_creator(current_user)
        flash[:notice] = success_message(_('plan'), _('copied'))
        format.js { render js: "window.location='#{plan_url(@plan)}?editing=true'" }
        # format.html { redirect_to @plan, notice: _('Plan was successfully duplicated.') }
        # format.json { head :no_content }
      else
        flash[:alert] = failed_create_error(@plan, 'Plan')
        format.js {}
      end
    end
  end
  
  # AJAX access to update the plan's visibility
  # POST /plans/:id
  def visibility
    plan = Plan.find(params[:id])
    authorize plan
    plan.visibility = "#{plan_params[:visibility]}"
    if plan.save
      render json: {code: 1, msg: ''}
    else
      render json: {code: 0, msg: _("Unable to change the plan's Test status")}
    end
  end
  

  private

  def plan_params
    params.require(:plan).permit(:org_id, :org_name, :funder_id, :funder_name, :template_id, :title, :visibility)
  end

  # different versions of the same template have the same dmptemplate_id
  # but different version numbers so for each set of templates with the
  # same dmptemplate_id choose the highest version number.
  def get_most_recent( templates )
    groups = Hash.new
    templates.each do |t|
      k = t.dmptemplate_id
      if !groups.has_key?(k)
        groups[k] = t
      else
        other = groups[k]
        if other.version < t.version
          groups[k] = t
        end
      end
    end
    groups.values
  end


  def fixup_hash(plan)
    rollup(plan, "notes", "answer_id", "answers")
    rollup(plan, "answers", "question_id", "questions")
    rollup(plan, "questions", "section_id", "sections")
    rollup(plan, "sections", "phase_id", "phases")

    plan["template"]["phases"] = plan.delete("phases")

    ghash = {}
    plan["guidance_groups"].map{|g| ghash[g["id"]] = g}
    plan["plans_guidance_groups"].each do |pgg|
      pgg["guidance_group"] = ghash[ pgg["guidance_group_id"] ]
    end

    plan["template"]["org"] = Org.find(plan["template"]["org_id"]).serializable_hash()
  end


  # find all object under src_plan_key
  # merge them into the items under obj_plan_key using
  # super_id = id
  # so we have answers which each have a question_id
  # rollup(plan, "answers", "quesiton_id", "questions")
  # will put the answers into the right questions.
  def rollup(plan, src_plan_key, super_id, obj_plan_key)
    id_to_obj = Hash.new()
    plan[src_plan_key].each do |o|
      id = o[super_id]
      if !id_to_obj.has_key?(id)
        id_to_obj[id] = Array.new
      end
      id_to_obj[id]  << o
    end

    plan[obj_plan_key].each do |o|
      id = o["id"]
      if id_to_obj.has_key?(id)
        o[src_plan_key] = id_to_obj[ id ]
      end
    end
    plan.delete(src_plan_key)
  end

  # Collect all of the templates available for the org+funder combination
  # --------------------------------------------------------------------------
  def template_options(org_id, funder_id)
    @templates = []

    if !org_id.blank? || !funder_id.blank?
      if funder_id.blank?
        # Load the org's template(s)
        unless org_id.nil?
          org = Org.find(org_id)
          @templates = Template.valid.where(published: true, org: org, customization_of: nil).to_a
          @msg = _("We found multiple DMP templates corresponding to the research organisation.") if @templates.count > 1
        end

      else
        funder = Org.find(funder_id)
        # Load the funder's template(s)
        @templates = Template.valid.where(published: true, org: funder).to_a

        unless org_id.blank?
          org = Org.find(org_id)

          # Swap out any organisational cusotmizations of a funder template
          @templates.each do |tmplt|
            customization = Template.valid.find_by(published: true, org: org, customization_of: tmplt.dmptemplate_id)
            unless customization.nil?
              @templates.delete(tmplt)
              @templates << customization
            end
          end
        end

        msg = _("We found multiple DMP templates corresponding to the funder.") if @templates.count > 1
      end
    end

    # If no templates were available use the generic templates
    if @templates.empty?
      @msg = _("Using the generic Data Management Plan")
      @templates << Template.find_by(is_default: true)
    end

    @templates = @templates.sort{|x,y| x.title <=> y.title } if @templates.count > 1
  end

end