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)
@selected_guidance_groups = @plan.guidance_groups
# 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
# If this is one of the already selected guidance groups its important!
if !(ggs & @selected_guidance_groups).empty?
@important_ggs << [org,ggs]
@all_ggs_grouped_by_org.delete(org)
end
end
# Sort the rest by org name for the accordion
@important_ggs = @important_ggs.sort_by{|org,gg| org.name}
@all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name}
@selected_guidance_groups = @selected_guidance_groups.collect{|gg| gg.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
=begin
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
=end
# PUT /plans/1
# PUT /plans/1.json
def update
@plan = Plan.find(params[:id])
authorize @plan
attrs = plan_params
# Save the guidance group selections
guidance_group_ids = params[:guidance_group_ids].blank? ? [] : params[:guidance_group_ids].map(&:to_i)
save_guidance_selections(guidance_group_ids)
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
# TODO: Do we need this is selections are saved with rest of form?
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)
save_guidance_selections(guidance_group_ids)
@plan.save
flash[:notice] = success_message(_('guidance choices'), _('saved'))
redirect_to action: "show"
end
def share
@plan = Plan.find(params[:id])
authorize @plan
@visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility
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,
:grant_number, :description, :identifier, :principal_investigator,
:principal_investigator_email, :principal_investigator_identifier,
:data_contact, :data_contact_email, :guidance_group_ids)
end
def save_guidance_selections(guidance_group_ids)
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
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