diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb deleted file mode 100644 index 9191895..0000000 --- a/app/controllers/annotations_controller.rb +++ /dev/null @@ -1,78 +0,0 @@ -class AnnotationsController < ApplicationController - respond_to :html - after_action :verify_authorized - - #update a example answer of a template - def admin_update - question = Question.includes(section: { phase: :template}).find(params[:question_id]) - authorize question - -# example_answer = Annotation.find_or_create_by(question: question, org: current_user.org, type: Annotation.types[:example_answer]) -# guidance = Annotation.find_or_create_by(question: question, org: current_user.org, type: Annotation.types[:guidance]) - - hash = {question_id: question.id, org_id: current_user.org.id} - process_changes(hash.merge({type: Annotation.types[:example_answer]}), params[:example_answer_text], _('example answer')) - process_changes(hash.merge({type: Annotation.types[:guidance]}), params[:guidance_text], _('guidance')) - - if !flash[:notice].blank? || !flash[:alert].blank? - template = question.section.phase.template - template.dirty = true - template.save! - end - tab = params[:r] || 'all-templates' - redirect_to "#{admin_show_phase_path(question.section.phase.id)}?section_id=#{question.section.id}&r=#{tab}" - end - - #delete an annotation - def admin_destroy - annotation = Annotation.find(params[:id]) - authorize annotation - parent_ids = Annotation.joins("INNER JOIN questions ON annotations.question_id = questions.id").joins("INNER JOIN sections ON questions.section_id = sections.id").joins("INNER JOIN phases ON sections.phase_id = phases.id").where("annotations.id": params[:id]).pluck("phases.id", "sections.id").first #annotation.question.section.phase.id - if annotation.present? - type = (annotation.type == Annotation.types[:example_answer] ? 'example answer' : 'guidance') - if annotation.destroy! - flash[:notice] = success_message(type, _('deleted')) - else - flash[:alert] = failed_destroy_error(annotation, type) - end - end - tab = params[:r] || 'all-templates' - redirect_to "#{admin_show_phase_path(parent_ids[0])}?section_id=#{parent_ids[1]}&r=#{tab}" - end - - private - - def init_annotation(text, question, org, type) - annotation = Annotation.new - annotation.org = org - annotation.question = question - annotation.text = text - annotation.type = type - return annotation - end - - def process_changes(hash, input, type) - # If the input is available update the annotation otherwise remove it if it exists - if input.present? - annotation = Annotation.find_or_create_by(hash) - if annotation.text != input - annotation.text = input - if annotation.save! - flash[:notice] = "#{(flash[:notice].nil? ? '' : flash[:notice] + '
')}#{success_message(type, _('updated'))}" - else - flash[:alert] = "#{(flash[:alert].nil? ? '' : flash[:alert] + '
')}#{failed_update_error(annotation, type)}" - end - end - else - # If the user cleared the text and the record exists, delete it - annotation = Annotation.find_by(hash) - if annotation.present? - if annotation.destroy! - flash[:notice] = "#{(flash[:notice].nil? ? '' : flash[:notice] + '
')}#{success_message(type, _('removed'))}" - else - flash[:alert] = "#{(flash[:alert].nil? ? '' : flash[:alert] + '
')}#{failed_update_error(annotation, type)}" - end - end - end - end -end diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index abc63af..07e82cd 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -72,7 +72,7 @@ }, "section" => { "id" => @section.id, - "progress" => render_to_string(partial: '/sections/progress', locals: { section: @section, plan: @plan }, formats: [:html]) + "progress" => render_to_string(partial: '/org_admin/sections/progress', locals: { section: @section, plan: @plan }, formats: [:html]) }, "plan" => { "id" => @plan.id, diff --git a/app/controllers/api/v0/plans_controller.rb b/app/controllers/api/v0/plans_controller.rb index 23ae557..156782f 100644 --- a/app/controllers/api/v0/plans_controller.rb +++ b/app/controllers/api/v0/plans_controller.rb @@ -28,7 +28,7 @@ if @template.customization_of.nil? @plan.funder_name = @template.org.name else - @plan.funder_name = Template.where(dmptemplate_id: @template.customization_of).first.org.name + @plan.funder_name = Template.where(family_id: @template.customization_of).first.org.name end @plan.template = @template @plan.title = params[:plan][:title] diff --git a/app/controllers/api/v0/statistics_controller.rb b/app/controllers/api/v0/statistics_controller.rb index 761cf26..a826122 100644 --- a/app/controllers/api/v0/statistics_controller.rb +++ b/app/controllers/api/v0/statistics_controller.rb @@ -136,7 +136,7 @@ if @templates[template.title].blank? @templates[template.title] = {} @templates[template.title][:title] = template.title - @templates[template.title][:id] = template.dmptemplate_id + @templates[template.title][:id] = template.family_id if template.plans.present? @templates[template.title][:uses] = restrict_date_range(template.plans).length else @@ -173,7 +173,7 @@ if @templates[plan.template.title].blank? @templates[plan.template.title] = {} @templates[plan.template.title][:title] = plan.template.title - @templates[plan.template.title][:id] = plan.template.dmptemplate_id + @templates[plan.template.title][:id] = plan.template.family_id @templates[plan.template.title][:uses] = 1 else @templates[plan.template.title][:uses] += 1 diff --git a/app/controllers/api/v0/templates_controller.rb b/app/controllers/api/v0/templates_controller.rb index 8560068..03999f8 100644 --- a/app/controllers/api/v0/templates_controller.rb +++ b/app/controllers/api/v0/templates_controller.rb @@ -13,31 +13,31 @@ @org_templates = {} - published_templates = Template.includes(:org).valid.where(customization_of: nil, published: true).order(:org_id, :version) - customized_templates = Template.includes(:org).valid.where(org_id: @user.org_id, published: true).where.not(customization_of: nil) + published_templates = Template.includes(:org).unarchived.where(customization_of: nil, published: true).order(:org_id, :version) + customized_templates = Template.includes(:org).unarchived.where(org_id: @user.org_id, published: true).where.not(customization_of: nil) - published_templates.each do |temp| + Template.published.order(:org_id, :version).each do |temp| if @org_templates[temp.org].present? - if @org_templates[temp.org][:own][temp.dmptemplate_id].nil? - @org_templates[temp.org][:own][temp.dmptemplate_id] = temp + if @org_templates[temp.org][:own][temp.family_id].nil? + @org_templates[temp.org][:own][temp.family_id] = temp end else @org_templates[temp.org] = {} @org_templates[temp.org][:own] = {} @org_templates[temp.org][:cust] = {} - @org_templates[temp.org][:own][temp.dmptemplate_id] = temp + @org_templates[temp.org][:own][temp.family_id] = temp end end - customized_templates.each do |temp| + Template.published_customization(@user.org_id).each do |temp| if @org_templates[temp.org].present? - if @org_templates[temp.org][:cust][temp.dmptemplate_id].nil? - @org_templates[temp.org][:cust][temp.dmptemplate_id] = temp + if @org_templates[temp.org][:cust][temp.family_id].nil? + @org_templates[temp.org][:cust][temp.family_id] = temp end else @org_templates[temp.org] = {} @org_templates[temp.org][:own] = {} @org_templates[temp.org][:cust] = {} - @org_templates[temp.org][:cust][temp.dmptemplate_id] = temp + @org_templates[temp.org][:cust][temp.family_id] = temp end end respond_with @org_templates diff --git a/app/controllers/concerns/paginable.rb b/app/controllers/concerns/paginable.rb index b9ab72b..d07f4d4 100644 --- a/app/controllers/concerns/paginable.rb +++ b/app/controllers/concerns/paginable.rb @@ -26,7 +26,6 @@ @paginable_params = query_params.symbolize_keys.merge(@paginable_params) # if duplicate keys, those from @paginable_params take precedence # Additional path_params passed to this function got special treatment (e.g. it is taking into account when building base_url) @paginable_path_params = path_params.symbolize_keys - if @paginable_params[:page] == 'ALL' && @paginable_params[:search].blank? && @paginable_options[:view_all] == false render(status: :forbidden, html: _('Restricted access to View All the records')) else @@ -103,15 +102,23 @@ def sort_link_url(sort_field) page = @paginable_params[:page] == 'ALL' ? 'ALL' : 1 if @paginable_params[:sort_field] == sort_field - return paginable_base_url_with_query_params( - page: page, - sort_field: sort_field, - sort_direction: swap_sort_direction) + sort_url = paginable_base_url_with_query_params( + page: page, + sort_field: sort_field, + sort_direction: swap_sort_direction) else - return paginable_base_url_with_query_params( + sort_url = paginable_base_url_with_query_params( page: page, sort_field: sort_field) end + return "#{sort_url}#{stringify_nonpagination_query_params}" + end + # Retrieve any query params that are not a part of the paginable concern + def stringify_nonpagination_query_params + other_params = @paginable_params.select do |param| + ![:page, :sort_field, :sort_direction, :search, :controller, :action].include?(param) + end + return other_params.empty? ? '' : "&#{other_params.collect{ |k, v| "#{k}=#{v}" }.join('&')}" end def stringify_query_params( search: @paginable_params[:search], diff --git a/app/controllers/concerns/versionable.rb b/app/controllers/concerns/versionable.rb new file mode 100644 index 0000000..676d4d2 --- /dev/null +++ b/app/controllers/concerns/versionable.rb @@ -0,0 +1,106 @@ +module Versionable + extend ActiveSupport::Concern + + included do + ## + # Takes in a Template, phase, Section, Question, or Annotaion + # IF the template is published, generates a new template + # finds the passed object in the new template + # @param obj - Template, Phase, Section, Question, Annotation + # @return type_of(obj) + def get_modifiable(obj) + if obj.respond_to?(:template) + template = obj.template + elsif obj.is_a?(Template) + template = obj + else + raise ArgumentError, _('obj should be a Template, Phase, Section, Question, or Annotation') + end + + new_template = Template.find_or_generate_version!(template) # raises RuntimeError if template is not latest + + if new_template != template + if obj.is_a?(Template) + obj = new_template + else + obj = find_in_space(obj,new_template.phases) + end + end + return obj + end + ## + # Takes in a phase, Section, Question, or Annotation which is newly + # generated and returns a modifiable version of that object + # NOTE: the obj passed is still not saved however it should belongs to a parent already + def get_new(obj) + raise ArgumentError, _('obj should be a Phase, Section, Question, or Annotation') unless obj.respond_to?(:template) + + template = obj.template + new_template = Template.find_or_generate_version!(template) # raises RuntimeError if template is not latest + + if new_template != template # Copied version + case obj + when Phase + belongs = :template + when Section + belongs = :phase + when Question + belongs = :section + when Annotation + belongs = :question + else + raise ArgumentError, _('obj should be a Phase, Section, Question, or Annotation') + end + + if belongs == :template + obj = obj.send(:deep_copy) + obj.template = new_template + else + found = find_in_space(obj.send(belongs), new_template.phases) + obj = obj.send(:deep_copy) + obj.send("#{belongs}=", found) + end + end + return obj + end + end + + private + # Locates an object (e.g. phase, section, question, annotation) in a search_space + # (e.g. phases/sections/questions/annotations) by comparing either the number method or + # the org_id and text for annotations + def find_in_space(obj, search_space) + raise ArgumentError, _('The search_space does not respond to each') unless search_space.respond_to?(:each) + raise ArgumentError, _('The search space does not have elements associated') unless search_space.length > 0 + + if obj.is_a?(search_space.first.class) + if obj.respond_to?(:number) # object is an instance of Phase, Section or Question + return search_space.find{ |search| search.number == obj.number } + elsif obj.respond_to?(:org_id) && obj.respond_to?(:text) # object is an instance of Annotation + return search_space.find{ |annotation| annotation.org_id == obj.org_id && annotation.text == obj.text } + end + return nil + end + + case search_space.first + when Phase + number = obj.phase.number + relation = :sections + when Section + number = obj.section.number + relation = :questions + when Question + number = obj.question.number + relation = :annotations + else + return nil + end + + search_space = search_space.find{ |search| search.number == number } + + if search_space.present? + return find_in_space(obj, search_space.send(relation)) + end + return nil + end +end diff --git a/app/controllers/guidances_controller.rb b/app/controllers/guidances_controller.rb index 357889f..19e2b89 100644 --- a/app/controllers/guidances_controller.rb +++ b/app/controllers/guidances_controller.rb @@ -36,11 +36,6 @@ guidance = Guidance.new(guidance_params) authorize guidance guidance.text = params["guidance-text"] - - guidance.themes = [] - if !guidance_params[:theme_ids].nil? - guidance_params[:theme_ids].map{|t| guidance.themes << Theme.find(t.to_i) unless t.empty? } - end if guidance.save @@ -68,7 +63,6 @@ guidance.text = params["guidance-text"] attrs = guidance_params - attrs[:theme_ids] = [] unless attrs[:theme_ids] if guidance.update_attributes(attrs) if guidance.published? diff --git a/app/controllers/org_admin/phases_controller.rb b/app/controllers/org_admin/phases_controller.rb new file mode 100644 index 0000000..827f4f3 --- /dev/null +++ b/app/controllers/org_admin/phases_controller.rb @@ -0,0 +1,155 @@ +module OrgAdmin + class PhasesController < ApplicationController + include Versionable + + after_action :verify_authorized + + # GET /org_admin/templates/:template_id/phases/[:id] + def show + phase = Phase.includes(:template, :sections).order(:number).find(params[:id]) + authorize phase + if !phase.template.latest? + flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') + end + section = params.fetch(:section, nil) + render('container', + locals: { + partial_path: 'show', + template: phase.template, + phase: phase, + sections: phase.sections.order(:number).select(:id, :title, :modifiable), + current_section: section.present? ? Section.find_by(id: section, phase_id: phase.id) : nil + }) + end + + # GET /org_admin/templates/:template_id/phases/[:id]/edit + def edit + phase = Phase.includes(:template).find(params[:id]) + authorize phase + if !phase.template.latest? + flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') + end + section = params.fetch(:section, nil) + # User cannot edit a phase if its a customization so redirect to show + if phase.template.customization_of.present? + redirect_to org_admin_template_phase_path(template_id: phase.template, id: phase.id, section: section) + else + render('container', + locals: { + partial_path: 'edit', + template: phase.template, + phase: phase, + sections: phase.sections.order(:number).select(:id, :title, :modifiable), + current_section: section.present? ? Section.find_by(id: section, phase_id: phase.id) : nil + }) + end + end + + #preview a phase + # GET /org_admin/phases/[:id]/preview + def preview + phase = Phase.includes(:template).find(params[:id]) + authorize phase + render('/org_admin/phases/preview', + locals: { + template: phase.template, + phase: phase + }) + end + + #add a new phase to a passed template + # GET /org_admin/phases/new + def new + template = Template.includes(:phases).find(params[:template_id]) + if template.latest? + nbr = template.phases.maximum(:number) + phase = Phase.new({ + template: template, + modifiable: true, + number: (nbr.present? ? nbr + 1 : 1) + }) + authorize phase + render('/org_admin/templates/container', + locals: { + partial_path: 'new', + template: template, + phase: phase, + referrer: request.referrer.present? ? request.referrer : org_admin_templates_path + }) + else + render org_admin_templates_path, alert: _('You canot add a phase to a historical version of a template.') + end + end + + #create a phase + # POST /org_admin/phases + def create + phase = Phase.new(phase_params) + phase.template = Template.find(params[:template_id]) + authorize phase + begin + phase = get_new(phase) + phase.modifiable = true + if phase.save! + flash[:notice] = success_message(_('phase'), _('created')) + else + flash[:alert] = failed_create_error(phase, _('phase')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + if flash[:alert].present? + redirect_to edit_org_admin_template_path(id: phase.template_id) + else + redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id) + end + end + + #update a phase of a template + # PUT /org_admin/phases/[:id] + def update + phase = Phase.find(params[:id]) + authorize phase + begin + phase = get_modifiable(phase) + if phase.update!(phase_params) + flash[:notice] = success_message(_('phase'), _('updated')) + else + flash[:alert] = failed_update_error(phase, _('phase')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id) + end + + #delete a phase + # DELETE org_admin/phases/[:id] + def destroy + phase = Phase.includes(:template).find(params[:id]) + authorize phase + begin + phase = get_modifiable(phase) + template = phase.template + if phase.destroy! + flash[:notice] = success_message(_('phase'), _('deleted')) + else + flash[:alert] = failed_destroy_error(phase, _('phase')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + + if flash[:alert].present? + redirect_to org_admin_template_phase_path(template.id, phase.id) + else + redirect_to edit_org_admin_template_path(template) + end + end + + private + def phase_params + params.require(:phase).permit(:title, :description, :number) + end + end +end \ No newline at end of file diff --git a/app/controllers/org_admin/questions_controller.rb b/app/controllers/org_admin/questions_controller.rb new file mode 100644 index 0000000..58a6ce5 --- /dev/null +++ b/app/controllers/org_admin/questions_controller.rb @@ -0,0 +1,148 @@ +module OrgAdmin + class QuestionsController < ApplicationController + include Versionable + + respond_to :html + after_action :verify_authorized + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/question/[:id] + def show + question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) + authorize question + render partial: 'show', locals: { + template: question.section.phase.template, + section: question.section, + question: question + } + end + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/question/[:id]/edit + def edit + question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) + authorize question + render partial: 'edit', locals: { + template: question.section.phase.template, + section: question.section, + question: question + } + end + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/new + def new + section = Section.includes(:questions, phase: :template).find(params[:section_id]) + nbr = section.questions.maximum(:number) + question = Question.new({ section_id: section.id, question_format: QuestionFormat.find_by(title: 'Text area'), number: nbr.present? ? nbr + 1 : 1 }) + authorize question + render partial: 'form', locals: { + template: section.phase.template, + section: section, + question: question, + method: 'post', + url: org_admin_template_phase_section_questions_path(template_id: section.phase.template.id, phase_id: section.phase.id, id: section.id) + } + end + + # POST /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions + def create + question = Question.new(question_params.merge({ section_id: params[:section_id] })) + authorize question + begin + question = get_new(question) + section = question.section + if question.save! + flash[:notice] = success_message(_('question'), _('created')) + else + flash[:alert] = failed_create_error(question, _('question')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + end + + # PUT /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/[:id] + def update + question = Question.find(params[:id]) + authorize question + begin + question = get_modifiable(question) + # Need to reattach the incoming annotation's and question_options to the + # modifiable (versioned) question + attrs = question_params + attrs = transfer_associations(question) if question.id != params[:id] + if question.update!(attrs) + flash[:notice] = success_message(_('question'), _('updated')) + else + flash[:alert] = failed_update_error(question, _('question')) + end + rescue StandardError => e + puts e.message + flash[:alert] = _('Unable to create a new version of this template.') + end + if question.section.phase.template.customization_of.present? + redirect_to org_admin_template_phase_path({ + template_id: question.section.phase.template.id, + id: question.section.phase.id, + section: question.section.id + }) + else + redirect_to edit_org_admin_template_phase_path({ + template_id: question.section.phase.template.id, + id: question.section.phase.id, + section: question.section.id + }) + end + end + + # DELETE /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/[:id] + def destroy + question = Question.find(params[:id]) + authorize question + begin + question = get_modifiable(question) + section = question.section + if question.destroy! + flash[:notice] = success_message(_('question'), _('deleted')) + else + flash[:alert] = failed_destroy_error(question, 'question') + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + redirect_to edit_org_admin_template_phase_path({ + template_id: section.phase.template.id, + id: section.phase.id, + section: section.id + }) + end + + private + def question_params + params.require(:question).permit(:number, :text, :question_format_id, :option_comment_display, :default_value, question_options_attributes: [:id, :number, :text, :is_default, :_destroy], annotations_attributes: [:id, :text, :org_id, :org, :type], theme_ids: []) + end + + # When a template gets versioned by changes to one of its questions we need to loop + # through the incoming params and ensure that the annotations and question_options + # get attached to the new question + def transfer_associations(question) + attrs = question_params + if attrs[:annotations_attributes].present? + attrs[:annotations_attributes].each_key do |key| + old_annotation = question.annotations.select do |a| + a.org_id.to_s == attrs[:annotations_attributes][key][:org_id] && a.type.to_s == attrs[:annotations_attributes][key][:type] + end + attrs[:annotations_attributes][key][:id] = old_annotation.first.id unless old_annotation.empty? + end + end + # TODO: This question_options id swap feel fragile. We cannot really match on any of the + # data elements because the user may have changed them so we rely on its position + # within the array/query since they should be equivalent. + if attrs[:question_options_attributes].present? + attrs[:question_options_attributes].each_key do |key| + attrs[:question_options_attributes][key][:id] = question.question_options[key.to_i].id.to_s if question.question_options[key.to_i].present? + end + end + attrs + end + end +end \ No newline at end of file diff --git a/app/controllers/org_admin/sections_controller.rb b/app/controllers/org_admin/sections_controller.rb new file mode 100644 index 0000000..27e1b14 --- /dev/null +++ b/app/controllers/org_admin/sections_controller.rb @@ -0,0 +1,122 @@ +module OrgAdmin + class SectionsController < ApplicationController + include Versionable + + respond_to :html + after_action :verify_authorized + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections + def index + authorize Section.new + phase = Phase.includes(:template, :sections).find(params[:phase_id]) + edit = phase.template.latest? && (current_user.can_modify_templates? && (phase.template.org_id == current_user.org_id)) + render partial: 'index', + locals: { + template: phase.template, + phase: phase, + sections: phase.sections, + current_section: phase.sections.first, + modifiable: edit, + edit: edit + } + end + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id] + def show + section = Section.find(params[:id]) + authorize section + section = Section.includes(questions: [:annotations, :question_options]).find(params[:id]) + render partial: 'show', locals: { + template: Template.find(params[:template_id]), + section: section + } + end + + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id]/edit + def edit + section = Section.includes({phase: :template}, questions: [:question_options, { annotations: :org }]).find(params[:id]) + authorize section + render partial: 'edit', + locals: { + template: section.phase.template, + phase: section.phase, + section: section + } + end + + # POST /org_admin/templates/[:template_id]/phases/[:phase_id]/sections + def create + phase = Phase.find(params[:phase_id]) + if phase.present? + section = Section.new(section_params.merge({ phase_id: phase.id })) + authorize section + begin + section = get_new(section) + if section.save + flash[:notice] = success_message(_('section'), _('created')) + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + else + flash[:alert] = failed_create_error(section, _('section')) + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id) + end + else + flash[:alert] = _('Unable to create a new section because the phase you specified does not exist.') + redirect_to edit_org_admin_template_path(template_id: params[:template_id]) + end + end + + # PUT /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id] + def update + section = Section.includes(phase: :template).find(params[:id]) + authorize section + begin + section = get_modifiable(section) + if section.update!(section_params) + flash[:notice] = success_message(_('section'), _('saved')) + else + flash[:alert] = failed_update_error(section, _('section')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + + if flash[:alert].present? + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + else + redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + end + end + + # DELETE /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id] + def destroy + section = Section.includes(phase: :template).find(params[:id]) + authorize section + begin + section = get_modifiable(section) + phase = section.phase + if section.destroy! + flash[:notice] = success_message(_('section'), _('deleted')) + else + flash[:alert] = failed_destroy_error(section, _('section')) + end + rescue StandardError => e + flash[:alert] = _('Unable to create a new version of this template.') + end + + if flash[:alert].present? + redirect_to(edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id)) + else + redirect_to(edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id)) + end + end + + private + def section_params + params.require(:section).permit(:title, :description, :number) + end + end +end \ No newline at end of file diff --git a/app/controllers/org_admin/templates_controller.rb b/app/controllers/org_admin/templates_controller.rb index 0da5044..bea37d6 100644 --- a/app/controllers/org_admin/templates_controller.rb +++ b/app/controllers/org_admin/templates_controller.rb @@ -1,392 +1,299 @@ module OrgAdmin class TemplatesController < ApplicationController include Paginable - include TemplateFilter + include Versionable after_action :verify_authorized + # The root version of index which returns all templates # GET /org_admin/templates # ----------------------------------------------------- def index authorize Template - - # Apply scoping - all_templates_hash = apply_scoping(params[:scope] || 'all', false, true) - own_hash = apply_scoping(params[:scope] || 'all', false, false) - customizable_hash = apply_scoping(params[:scope] || 'all', true, false) - - # Apply pagination - all_templates_hash[:templates] = all_templates_hash[:templates].page(1) - own_hash[:templates] = own_hash[:templates].page(1) - customizable_hash[:templates] = customizable_hash[:templates].page(1) - - # Gather up all of the publication dates for the live versions of each template. - published = get_publication_dates(all_templates_hash[:scopes][:dmptemplate_ids]) - - render 'index', locals: { - all_templates: all_templates_hash[:templates], - customizable_templates: customizable_hash[:templates], - own_templates: own_hash[:templates], - customized_templates: customizable_hash[:customizations], - published: published, - current_org: current_user.org, + templates = Template.latest_version.where(customization_of: nil) + published = templates.select{|t| t.published? || t.draft? }.length + render 'index', locals: { orgs: Org.all, - current_tab: params[:r], - scopes: { all: all_templates_hash[:scopes], orgs: own_hash[:scopes], funders: customizable_hash[:scopes] } + title: _('All Templates'), + templates: templates.includes(:org), + action: 'index', + query_params: { sort_field: :title, sort_direction: :asc }, + all_count: templates.length, + published_count: published.present? ? published : 0, + unpublished_count: published.present? ? (templates.length - published): templates.length } 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 + title = current_user.can_super_admin? ? _('%{org_name} Templates') % { org_name: current_user.org.name } : _('Own Templates') + render 'index', locals: { + orgs: current_user.can_super_admin? ? Org.all : nil, + title: title, + templates: templates, + action: 'organisational', + query_params: { sort_field: :title, sort_direction: :asc }, + all_count: templates.length, + published_count: published.present? ? published : 0, + unpublished_count: published.present? ? (templates.length - published): templates.length + } + 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 + render 'index', locals: { + orgs: (current_user.can_super_admin? ? Org.all : []), + title: _('Customizable Templates'), + templates: funder_templates, + customizations: customizations, + action: 'customisable', + query_params: { sort_field: :title, sort_direction: :asc }, + all_count: funder_templates.length, + published_count: published.present? ? published : 0, + unpublished_count: published.present? ? (customizations.length - published): customizations.length, + not_customized_count: funder_templates.length - customizations.length + } + 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? + flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') + 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? + flash[:notice] = _("You are viewing a historical version of this #{template_type(template)}. You will not be able to make changes.") + end + render 'container', locals: { + partial_path: 'edit', + template: template, + phases: phases, + referrer: get_referrer(template, request.referrer) } + end + # GET /org_admin/templates/new # ----------------------------------------------------- def new authorize Template - @current_tab = params[:r] || 'all-templates' + 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 dmptemplate_id - @template = Template.new(params[:template]) - @template.org_id = current_user.org.id - @template.description = params['template-desc'] - @template.links = (params["template-links"].present? ? JSON.parse(params["template-links"]) : {"funder": [], "sample_plan": []}) - - if @template.save - redirect_to edit_org_admin_template_path(@template), notice: success_message(@template.template_type, _('created')) + # 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 = (params["template-links"].present? ? ActiveSupport::JSON.decode(params["template-links"]) : {"funder": [], "sample_plan": []}) + if template.save! + redirect_to edit_org_admin_template_path(template), notice: success_message(template_type(template), _('created')) else - @hash = @template.to_hash - flash[:alert] = failed_create_error(@template, @template.template_type) - render action: "new" + flash[:alert] = failed_create_error(template, template_type(template)) + render partial: "org_admin/templates/new", locals: { template: template, hash: hash } end end - # GET /org_admin/templates/:id/edit - # ----------------------------------------------------- - def edit - @template = Template.includes(:org, phases: [sections: [questions: [:question_options, :question_format, :annotations]]]).find(params[:id]) - authorize @template - - @current = Template.current(@template.dmptemplate_id) - @current_tab = params[:r] || 'all-templates' - - if @template == @current - if @template.published? - new_version = @template.get_new_version - if !new_version.nil? - redirect_to(action: 'edit', id: new_version.id, r: @current_tab) - return - else - flash[:alert] = _("Unable to create a new version of this #{@template.template_type}. You are currently working with a published copy.") - end - end - else - flash[:notice] = _("You are viewing a historical version of this #{@template.template_type}. You will not be able to make changes.") - end - - # once the correct template has been generated, we convert it to hash - @template_hash = @template.to_hash - - render('container', - locals: { - partial_path: 'edit', - template: @template, - current: @current, - edit: @template == @current, - template_hash: @template_hash, - current_tab: @current_tab - }) - end - # PUT /org_admin/templates/:id (AJAXable) # ----------------------------------------------------- def update - @template = Template.find(params[:id]) - authorize @template # NOTE if non-authorized error is raised, it performs a redirect to root_path and no JSON output is generated - - current = Template.current(@template.dmptemplate_id) - - # Only allow the current version to be updated - if current != @template - render(status: :forbidden, json: { msg: _("You can not edit a historical version of this #{@template.template_type}.")}) - else - template_links = nil - begin - template_links = JSON.parse(params["template-links"]) if params["template-links"].present? - rescue JSON::ParserError - render(status: :bad_request, json: { msg: _("Error parsing links for a #{@template.template_type}") }) - return - end - - # TODO dirty check at template model instead of here for reusability, i.e. method dirty? passing a template object - if @template.description != params["template-desc"] || - @template.title != params[:template][:title] || - @template.links != template_links - @template.dirty = true - end - - @template.description = params["template-desc"] - @template.links = template_links if template_links.present? - - # If the visibility checkbox is not checked and the user's org is a funder set the visibility to public - # otherwise default it to organisationally_visible - if current_user.org.funder? && params[:template_visibility].nil? - @template.visibility = Template.visibilities[:publicly_visible] - else - @template.visibility = Template.visibilities[:organisationally_visible] - end - - if @template.update_attributes(params[:template]) - render(status: :ok, json: { msg: success_message(@template.template_type, _('saved'))}) + template = Template.find(params[:id]) + authorize template + begin + template.assign_attributes(template_params) + template.links = ActiveSupport::JSON.decode(params["template-links"]) if params["template-links"].present? + 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.
) and therefore the client should parse them accordingly - render(status: :bad_request, json: { msg: failed_update_error(@template, @template.template_type)}) + 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)}") }) and 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]) - current_tab = params[:r] || 'all-templates' - authorize @template - - if @template.plans.length <= 0 - current = Template.current(@template.dmptemplate_id) - - # Only allow the current version to be destroyed - if current == @template - if @template.destroy - flash[:notice] = success_message(@template.template_type, _('removed')) - redirect_to org_admin_templates_path(r: current_tab) + 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 - @hash = @template.to_hash - flash[:alert] = failed_destroy_error(@template, @template.template_type) - redirect_to org_admin_templates_path(r: current_tab) + flash[:alert] = failed_destroy_error(template, template_type(template)) end - else - flash[:alert] = _("You cannot delete historical versions of this #{@template.template_type}.") - redirect_to org_admin_templates_path(r: current_tab) end else - flash[:alert] = _("You cannot delete a #{@template.template_type} that has been used to create plans.") - redirect_to org_admin_templates_path(r: current_tab) + flash[:alert] = _("You cannot delete a #{template_type(template)} that has been used to create plans.") end + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end # GET /org_admin/templates/:id/history # ----------------------------------------------------- def history - @template = Template.find(params[:id]) - authorize @template - @templates = Template.where(dmptemplate_id: @template.dmptemplate_id) - @current = Template.current(@template.dmptemplate_id) - @current_tab = params[:r] || 'all-templates' + template = Template.find(params[:id]) + authorize template + templates = Template.where(family_id: template.family_id) + render 'history', locals: { + templates: templates, + referrer: template.customization_of.present? ? customisable_org_admin_templates_path : organisational_org_admin_templates_path, + current: templates.maximum(:version) + } end - # GET /org_admin/templates/:id/customize + # POST /org_admin/templates/:id/customize # ----------------------------------------------------- def customize - @template = Template.find(params[:id]) - @current_tab = params[:r] || 'all-templates' - authorize @template - - customisation = Template.deep_copy(@template) - customisation.org = current_user.org - customisation.version = 0 - customisation.customization_of = @template.dmptemplate_id - customisation.dmptemplate_id = loop do - random = rand 2147483647 - break random unless Template.exists?(dmptemplate_id: random) - end - customisation.dirty = true - customisation.save - - customisation.phases.includes(:sections, :questions).each do |phase| - phase.modifiable = false - phase.save! - phase.sections.each do |section| - section.modifiable = false - section.save! - section.questions.each do |question| - question.modifiable = false - question.save! - end + 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.') + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end + else + flash[:notice] = _('That template is not customizable.') + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end - - redirect_to edit_org_admin_template_path(customisation, r: 'funder-templates') end - # GET /org_admin/templates/:id/transfer_customization + # 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]) - @current_tab = params[:r] || 'all-templates' - authorize @template - new_customization = Template.deep_copy(@template) - new_customization.org_id = current_user.org_id - new_customization.published = false - new_customization.customization_of = @template.dmptemplate_id - new_customization.dirty = true - new_customization.phases.includes(sections: :questions).each do |phase| - phase.modifiable = false - phase.save - phase.sections.each do |section| - section.modifiable = false - section.save - section.questions.each do |question| - question.modifiable = false - question.save - end + 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.') + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end + else + flash[:notice] = _('That template is no longer customizable.') + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end - customizations = Template.includes(:org, phases:[sections: [questions: :annotations]]).where(org_id: current_user.org_id, customization_of: @template.dmptemplate_id).order(version: :desc) - # existing version to port over - max_version = customizations.first - new_customization.dmptemplate_id = max_version.dmptemplate_id - new_customization.version = max_version.version + 1 - # here we rip the customizations out of the old template - # First, we find any customzed phases or sections - max_version.phases.each do |phase| - # check if the phase was added as a customization - if phase.modifiable - # deep copy the phase and add it to the template - phase_copy = Phase.deep_copy(phase) - phase_copy.number = new_customization.phases.length + 1 - phase_copy.template_id = new_customization.id - phase_copy.save! - else - # iterate over the sections to see if any of them are customizations - phase.sections.each do |section| - if section.modifiable - # this is a custom section - section_copy = Section.deep_copy(section) - customization_phase = new_customization.phases.includes(:sections).where(number: phase.number).first - section_copy.phase_id = customization_phase.id - # custom sections get added to the end - section_copy.number = customization_phase.sections.length + 1 - # section from phase with corresponding number in the main_template - section_copy.save! - else - # not a customized section, iterate over questions - customization_phase = new_customization.phases.includes(sections: [questions: :annotations]).where(number: phase.number).first - customization_section = customization_phase.sections.where(number: section.number).first - section.questions.each do |question| - # find corresponding question in new template - customization_question = customization_section.questions.where(number: question.number).first - # apply annotations - question.annotations.where(org_id: current_user.org_id).each do |annotation| - annotation_copy = Annotation.deep_copy(annotation) - annotation_copy.question_id = customization_question.id - annotation_copy.save! - end - end - end - end - end - end - new_customization.save - redirect_to edit_org_admin_template_path(new_customization, r: 'funder-templates') end - # PUT /org_admin/templates/:id/copy (AJAX) + # POST /org_admin/templates/:id/copy (AJAX) # ----------------------------------------------------- def copy - @template = Template.find(params[:id]) - current_tab = params[:r] || 'all-templates' - authorize @template - - new_copy = Template.deep_copy(@template) - new_copy.title = "Copy of " + @template.title - new_copy.version = 0 - new_copy.published = false - new_copy.dmptemplate_id = loop do - random = rand 2147483647 - break random unless Template.exists?(dmptemplate_id: random) + 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)) + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end - - if new_copy.save - flash[:notice] = "#{@template.template_type.capitalize} was successfully copied." - redirect_to edit_org_admin_template_path(id: new_copy.id, edit: true, r: 'organisation-templates'), notice: _('Information was successfully created.') - else - flash[:alert] = failed_create_error(new_copy, @template.template_type) - end - end - # GET /org_admin/templates/:id/publish (AJAX) TODO convert to PUT verb + # PATCH /org_admin/templates/:id/publish (AJAX) # ----------------------------------------------------- def publish template = Template.find(params[:id]) authorize template - current = Template.current(template.dmptemplate_id) - - # Only allow the current version to be updated - if current != template - redirect_to org_admin_templates_path, alert: _("You can not publish a historical version of this #{template.template_type}.") - - else - # Unpublish the older published version if there is one - live = Template.live(template.dmptemplate_id) - if !live.nil? and self != live - live.published = false - live.save! + 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 - # Set the dirty flag to false - template.dirty = false - template.published = true - template.save - - flash[:notice] = _("Your #{template.template_type} has been published and is now available to users.") - redirect_to "#{org_admin_templates_path}#{template.template_type == _('customisation') ? '#funder-templates' : '#organisation-templates'}" + else + flash[:alert] = _("You can not publish a historical version of this #{template_type(template)}.") end + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end - # GET /org_admin/templates/:id/unpublish (AJAX) TODO convert to PUT verb + # PATCH /org_admin/templates/:id/unpublish (AJAX) # ----------------------------------------------------- def unpublish template = Template.find(params[:id]) authorize template - - if template.nil? - flash[:alert] = _("That #{template.template_type} is not currently published.") - else - template.published = false - template.save - flash[:notice] = _("Your #{template.template_type} is no longer published. Users will not be able to create new DMPs for this #{template.template_type} until you re-publish it") + 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 - - redirect_to "#{org_admin_templates_path}#{template.template_type == _('customisation') ? '#funder-templates' : '#organisation-templates'}" + flash[:notice] = _("Successfully unpublished your #{template_type(template)}") unless flash[:alert].present? + redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end - # PUT /org_admin/template_options (AJAX) + # 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) - funder_templates = Template.valid.publicly_visible.where(published: true, org_id: funder_id) - - if org_id.blank? - templates = funder_templates.to_a - else + # 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 - funder_templates.each do |tmplt| - customization = Template.valid.find_by(published: true, org_id: org_id, customization_of: tmplt.dmptemplate_id) - if customization.present? && tmplt.created_at < customization.created_at - templates << customization + 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 - templates << tmplt + tmplt end end end @@ -395,18 +302,21 @@ # 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.organisationally_visible.valid.where(published: true, org_id: org_id, customization_of: nil).to_a + 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? - templates << Template.where(is_default: true, published: true).first + 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 + templates = (templates.count > 0 ? templates.sort{|x,y| x.title <=> y.title} : []) - render json: {"templates": templates.collect{|t| {id: t.id, title: t.title} }}.to_json end @@ -416,5 +326,25 @@ 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) + if 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) + template.customization_of.present? ? customisable_org_admin_templates_path : organisational_org_admin_templates_path + else + request.referrer + end + else + org_admin_templates_path + end + end end end \ No newline at end of file diff --git a/app/controllers/paginable/templates_controller.rb b/app/controllers/paginable/templates_controller.rb index b3370c1..413f503 100644 --- a/app/controllers/paginable/templates_controller.rb +++ b/app/controllers/paginable/templates_controller.rb @@ -1,79 +1,65 @@ class Paginable::TemplatesController < ApplicationController include Paginable - include TemplateFilter - # GET /paginable/templates/all/:page (AJAX) + # GET /paginable/templates/:page (AJAX) # ----------------------------------------------------- - def all - raise Pundit::NotAuthorizedError unless Paginable::TemplatePolicy.new(current_user).all? - # Apply scoping - hash = apply_scoping(params[:scope] || 'all', false, true) - - # Apply pagination - hash[:templates] = hash[:templates].page(params[:page]) if params[:page] != 'ALL' - - # Gather up all of the publication dates for the live versions of each template. - published = get_publication_dates(hash[:scopes][:dmptemplate_ids]) - - paginable_renderise partial: 'all', - scope: hash[:templates], - locals: { current_org: current_user.org.id, - customizations: hash[:customizations], - published: published, - scopes: hash[:scopes]} + def index + authorize Template + templates = Template.latest_version.where(customization_of: nil) + case params[:f] + when 'published' + template_ids = templates.select{|t| t.published? || t.draft? }.collect(&:family_id) + templates = Template.latest_version(template_ids).where(customization_of: nil) + when 'unpublished' + template_ids = templates.select{|t| !t.published? && !t.draft? }.collect(&:family_id) + templates = Template.latest_version(template_ids).where(customization_of: nil) + end + paginable_renderise partial: 'index', scope: templates.includes(:org), locals: { action: 'index' } end - # GET /paginable/templates/funders/:page (AJAX) + # GET /paginable/templates/organisational/:page (AJAX) # ----------------------------------------------------- - def funders - raise Pundit::NotAuthorizedError unless Paginable::TemplatePolicy.new(current_user).funders? - # Apply scoping - hash = apply_scoping(params[:scope] || 'all', true, false) - - # Apply pagination - hash[:templates] = hash[:templates].page(params[:page]) if params[:page] != 'ALL' - - # Gather up all of the publication dates for the live versions of each template. - published = get_publication_dates(hash[:scopes][:dmptemplate_ids]) - - paginable_renderise partial: 'funders', - scope: hash[:templates], - locals: { current_org: current_user.org.id, - customizations: hash[:customizations], - published: published, - scopes: hash[:scopes] } + def organisational + authorize Template + templates = Template.latest_version_per_org(current_user.org.id).where(customization_of: nil, org_id: current_user.org.id) + case params[:f] + when 'published' + template_ids = templates.select{|t| t.published? || t.draft? }.collect(&:family_id) + templates = Template.latest_version(template_ids) + when 'unpublished' + template_ids = templates.select{|t| !t.published? && !t.draft? }.collect(&:family_id) + templates = Template.latest_version(template_ids) + end + paginable_renderise partial: 'organisational', scope: templates, locals: { action: 'organisational' } end - # GET /paginable/templates/orgs/:page (AJAX) + # GET /paginable/templates/customisable/:page (AJAX) # ----------------------------------------------------- - def orgs - raise Pundit::NotAuthorizedError unless Paginable::TemplatePolicy.new(current_user).orgs? - # Apply scoping - hash = apply_scoping(params[:scope] || 'all', false, false) - - # Apply pagination - hash[:templates] = hash[:templates].page(params[:page]) if params[:page] != 'ALL' - - # Gather up all of the publication dates for the live versions of each template. - published = get_publication_dates(hash[:scopes][:dmptemplate_ids]) - - paginable_renderise partial: 'orgs', - scope: hash[:templates], - locals: { current_org: current_user.org.id, - customizations: hash[:customizations], - published: published, - scopes: hash[:scopes]} + def customisable + authorize Template + customizations = Template.latest_customized_version_per_org(current_user.org.id) + templates = Template.latest_customizable + case params[:f] + when 'published' + customization_ids = customizations.select{|t| t.published? || t.draft? }.collect(&:customization_of) + templates = Template.latest_customizable.where(family_id: customization_ids) + when 'unpublished' + customization_ids = customizations.select{|t| !t.published? && !t.draft? }.collect(&:customization_of) + templates = Template.latest_customizable.where(family_id: customization_ids) + when 'not-customised' + templates = Template.latest_customizable.where.not(family_id: customizations.collect(&:customization_of)) + end + paginable_renderise partial: 'customisable', scope: templates.joins(:org).includes(:org), locals: { action: 'customisable', customizations: customizations } end # GET /paginable/templates/publicly_visible/:page (AJAX) # ----------------------------------------------------- def publicly_visible - templates = Template.live(Template.families(Org.funder.pluck(:id)).pluck(:dmptemplate_id)).publicly_visible.pluck(:id) << - Template.where(is_default: true).valid.published.pluck(:id) - + templates = Template.live(Template.families(Org.funder.pluck(:id)).pluck(:family_id)).publicly_visible.pluck(:id) << + Template.where(is_default: true).unarchived.published.pluck(:id) paginable_renderise( partial: 'publicly_visible', - scope: Template.includes(:org).where(id: templates.uniq.flatten).valid.published) + scope: Template.includes(:org).where(id: templates.uniq.flatten).published) end # GET /paginable/templates/:id/history/:page (AJAX) @@ -81,12 +67,11 @@ def history @template = Template.find(params[:id]) authorize @template - @templates = Template.where(dmptemplate_id: @template.dmptemplate_id) - @current = Template.current(@template.dmptemplate_id) + @templates = Template.where(family_id: @template.family_id) + @current = Template.current(@template.family_id) paginable_renderise( partial: 'history', scope: @templates, - query_params: { sort_field: :version, sort_direction: :desc }, - locals: { current: @current }) + locals: { current: @templates.maximum(:version) }) end end diff --git a/app/controllers/phases_controller.rb b/app/controllers/phases_controller.rb deleted file mode 100644 index f60cec0..0000000 --- a/app/controllers/phases_controller.rb +++ /dev/null @@ -1,186 +0,0 @@ -class PhasesController < ApplicationController - require 'pp' - - after_action :verify_authorized - - - # GET /plans/:plan_id/phases/:id/edit - def edit - plan = Plan.find(params[:plan_id]) - authorize plan - - plan, phase = Plan.load_for_phase(params[:plan_id], params[:id]) - - readonly = !plan.editable_by?(current_user.id) - - guidance_groups_ids = plan.guidance_groups.collect(&:id) - - guidance_groups = GuidanceGroup.where(published: true, id: guidance_groups_ids) - # Since the answers have been pre-fetched through plan (see Plan.load_for_phase) - # we create a hash whose keys are question id and value is the answer associated - answers = plan.answers.reduce({}){ |m, a| m[a.question_id] = a; m } - - render('/phases/edit', locals: { - base_template_org: phase.template.base_org, - plan: plan, phase: phase, readonly: readonly, - question_guidance: plan.guidance_by_question_as_hash, - guidance_groups: guidance_groups, - answers: answers }) - end - - - # GET /plans/PLANID/phases/PHASEID/status.json - def status - @plan = Plan.eager_load(params[:plan_id]) - authorize @plan - if user_signed_in? && @plan.readable_by?(current_user.id) then - respond_to do |format| - format.json { render json: @plan.status } - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end - - #show and edit a phase of the template - def admin_show - @phase = Phase.includes(:template, :sections).order(:number).find(params[:id]) - authorize @phase - - @current = Template.current(@phase.template.dmptemplate_id) - @edit = (@phase.template.org == current_user.org) && (@phase.template == @current) - - if params.has_key?(:question_id) - @question_id = params[:question_id].to_i - end - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - - if @phase.template != @current - flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') - end - - render('/org_admin/templates/container', - locals: { - partial_path: 'admin_show', - phase: @phase, - template: @phase.template, - edit: @edit, - current_section: params.has_key?(:section_id) ? params[:section_id].to_i : nil, - current_tab: params[:r] || 'all-templates' - }) - end - - - #preview a phase - def admin_preview - @phase = Phase.find(params[:id]) - authorize @phase - @template = @phase.template - @current_tab = params[:r] || 'all-templates' - @base_template_org = @phase.template.base_org - end - - - #add a new phase to a passed template - def admin_add - @template = Template.find(params[:id]) - @phase = Phase.new - @phase.template = @template - authorize @phase - @phase.number = @template.phases.count + 1 - render('/org_admin/templates/container', - locals: { - partial_path: 'admin_add', - template: @template, - edit: true, - current_tab: params[:r] || 'all-templates' - }) - end - - - #create a phase - def admin_create - @phase = Phase.new(params[:phase]) - authorize @phase - - @phase.description = params["phase-desc"] - @phase.modifiable = true - @current_tab = params[:r] || 'all-templates' - if @phase.save - @phase.template.dirty = true - @phase.template.save! - - redirect_to admin_show_phase_path(id: @phase.id, r: @current_tab), notice: success_message(_('phase'), _('created')) - else - flash[:alert] = failed_create_error(@phase, _('phase')) - @template = @phase.template - redirect_to edit_org_admin_template_path(id: @phase.template_id, r: @current_tab) - end - end - - - #update a phase of a template - def admin_update - @phase = Phase.find(params[:id]) - authorize @phase - - @phase.description = params["phase-desc"] - @current_tab = params[:r] || 'all-templates' - if @phase.update_attributes(params[:phase]) - @phase.template.dirty = true - @phase.template.save! - - redirect_to admin_show_phase_path(@phase, r: @current_tab), notice: success_message(_('phase'), _('saved')) - else - @sections = @phase.sections - @template = @phase.template - # These params may not be available in this context so they may need - # to be set to true without the check - @edit = true - @open = !params[:section_id].nil? - @section_id = (params[:section_id].nil? ? nil : params[:section_id].to_i) - @question_id = (params[:question_id].nil? ? nil : params[:question_id].to_i) - flash[:alert] = failed_update_error(@phase, _('phase')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(@phase, r: @current_tab) - end - end - - #delete a phase - def admin_destroy - @phase = Phase.find(params[:phase_id]) - authorize @phase - @template = @phase.template - @current_tab = params[:r] || 'all-templates' - if @phase.destroy - @template.dirty = true - @template.save! - - redirect_to edit_org_admin_template_path(@template, r: @current_tab), notice: success_message(_('phase'), _('deleted')) - else - @sections = @phase.sections - - # These params may not be available in this context so they may need - # to be set to true without the check - @edit = true - @open = !params[:section_id].nil? - @section_id = (params[:section_id].nil? ? nil : params[:section_id].to_i) - @question_id = (params[:question_id].nil? ? nil : params[:question_id].to_i) - flash[:alert] = failed_destroy_error(@phase, _('phase')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(@phase, r: @current_tab) - end - end -end diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index de4c2d5..9ab3e10 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -144,11 +144,35 @@ @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| (org.nil? ? '' : 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) + @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(family_id: @plan.template.customization_of).first) respond_to :html end + # GET /plans/:plan_id/phases/:id/edit + def edit + plan = Plan.find(params[:id]) + authorize plan + + plan, phase = Plan.load_for_phase(params[:id], params[:phase_id]) + + readonly = !plan.editable_by?(current_user.id) + + guidance_groups_ids = plan.guidance_groups.collect(&:id) + + guidance_groups = GuidanceGroup.where(published: true, id: guidance_groups_ids) + # Since the answers have been pre-fetched through plan (see Plan.load_for_phase) + # we create a hash whose keys are question id and value is the answer associated + answers = plan.answers.reduce({}){ |m, a| m[a.question_id] = a; m } + + render('/phases/edit', locals: { + base_template_org: phase.template.base_org, + plan: plan, phase: phase, readonly: readonly, + question_guidance: plan.guidance_by_question_as_hash, + guidance_groups: guidance_groups, + answers: answers }) + end + # PUT /plans/1 # PUT /plans/1.json def update @@ -360,13 +384,13 @@ end - # different versions of the same template have the same dmptemplate_id + # different versions of the same template have the same family_id # but different version numbers so for each set of templates with the - # same dmptemplate_id choose the highest version number. + # same family_id choose the highest version number. def get_most_recent( templates ) groups = Hash.new templates.each do |t| - k = t.dmptemplate_id + k = t.family_id if !groups.has_key?(k) groups[k] = t else diff --git a/app/controllers/public_pages_controller.rb b/app/controllers/public_pages_controller.rb index f64e61d..1258c2d 100644 --- a/app/controllers/public_pages_controller.rb +++ b/app/controllers/public_pages_controller.rb @@ -4,15 +4,15 @@ # GET template_index # ----------------------------------------------------- def template_index - templates = Template.live(Template.families(Org.funder.pluck(:id)).pluck(:dmptemplate_id)).publicly_visible.pluck(:id) << - Template.where(is_default: true).valid.published.pluck(:id) - @templates = Template.includes(:org).where(id: templates.uniq.flatten).valid.published.order(title: :asc).page(1) + templates = Template.live(Template.families(Org.funder.pluck(:id)).pluck(:family_id)).publicly_visible.pluck(:id) << + Template.where(is_default: true).unarchived.published.pluck(:id) + @templates = Template.includes(:org).where(id: templates.uniq.flatten).unarchived.published.order(title: :asc).page(1) end # GET template_export/:id # ----------------------------------------------------- def template_export - # only export live templates, id passed is dmptemplate_id + # only export live templates, id passed is family_id @template = Template.live(params[:id]) # covers authorization for this action. Pundit dosent support passing objects into scoped policies raise Pundit::NotAuthorizedError unless PublicPagePolicy.new( @template).template_export? diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb deleted file mode 100644 index fbbd17f..0000000 --- a/app/controllers/questions_controller.rb +++ /dev/null @@ -1,143 +0,0 @@ -class QuestionsController < ApplicationController - respond_to :html - after_action :verify_authorized - - #create a question - def admin_create - begin - @question = Question.new(question_params) - authorize @question - @question.modifiable = true - current_tab = params[:r] || 'all-templates' - if @question.question_format.textfield? - @question.default_value = params["question-default-value-textfield"] - elsif @question.question_format.textarea? - @question.default_value = params["question-default-value-textarea"] - end - if @question.save - @question.section.phase.template.dirty = true - @question.section.phase.template.save! - if params[:example_answer].present? - example_answer = Annotation.new({question_id: @question.id, org_id: current_user.org_id, text: params[:example_answer], type: Annotation.types[:example_answer]}) - example_answer.save - end - if params[:guidance].present? - guidance = Annotation.new({question_id: @question.id, org_id: current_user.org_id, text: params[:guidance], type: Annotation.types[:guidance]}) - guidance.save - end - redirect_to admin_show_phase_path(id: @question.section.phase_id, section_id: @question.section_id, question_id: @question.id, r: current_tab), notice: success_message(_('question'), _('created')) - else - @edit = (@question.section.phase.template.org == current_user.org) - @open = true - @phase = @question.section.phase - @section = @question.section - @sections = @phase.sections - @section_id = @question.section.id - @question_id = @question.id - - flash[:alert] = failed_create_error(@question, _('question')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(id: @question.section.phase_id, section_id: @question.section_id, r: current_tab) - end - rescue ActionController::ParameterMissing => e - flash[:alert] = e.message - end - end - - #update a question of a template - def admin_update - @question = Question.find(params[:id]) - authorize @question - - guidance = @question.get_guidance_annotation(current_user.org_id) - current_tab = params[:r] || 'all-templates' - if params["question-guidance-#{params[:id]}"].present? - unless guidance.present? - guidance = Annotation.new(type: :guidance, org_id: current_user.org_id, question_id: @question.id) - end - guidance.text = params["question-guidance-#{params[:id]}"] - guidance.save - else - # The user cleared out the guidance value so delete the record - guidance.destroy! if guidance.present? - end - example_answer = @question.get_example_answers(current_user.org_id).first - if params["question"]["annotations_attributes"].present? && params["question"]["annotations_attributes"]["0"]["id"].present? - unless example_answer.present? - example_answer = Annotation.new(type: :example_answer, org_id: current_user.org_id, question_id: @question.id) - end - example_answer.text = params["question"]["annotations_attributes"]["0"]["text"] - example_answer.save - else - # The user cleared out the example answer value so delete the record - example_answer.destroy if example_answer.present? - end - - if @question.question_format.textfield? - @question.default_value = params["question-default-value-textfield"] - elsif @question.question_format.textarea? - @question.default_value = params["question-default-value-textarea"] - end - @section = @question.section - @phase = @section.phase - template = @phase.template - - attrs = params[:question] - attrs[:theme_ids] = [] unless attrs[:theme_ids] - - if @question.update_attributes(attrs) - @phase.template.dirty = true - @phase.template.save! - - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, question_id: @question.id, r: current_tab), notice: success_message(_('question'), _('saved')) - else - @edit = (@phase.template.org == current_user.org) - @open = true - @sections = @phase.sections - @section_id = @section.id - @question_id = @question.id - - flash[:alert] = failed_update_error(@question, _('question')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, question_id: @question.id, r: current_tab) - end - end - - #delete question - def admin_destroy - @question = Question.find(params[:question_id]) - authorize @question - @section = @question.section - @phase = @section.phase - current_tab = params[:r] || 'all-templates' - if @question.destroy - @phase.template.dirty = true - @phase.template.save! - - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, r: current_tab), notice: success_message(_('question'), _('deleted')) - else - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, r: current_tab), alert: failed_destroy_error(@question, 'question') - end - end - - private - # Filters the valid attributes for a question according to each type. - # Note, that params[:question] and params[:question][:question_format_id] are required and their absence raises ActionController::ParameterMissing - def question_params - permitted = params.require(:question).except(:created_at, :updated_at).tap do |question_params| - question_params.require(:question_format_id) - q_format = QuestionFormat.find(question_params[:question_format_id]) - if !q_format.option_based? - question_params.delete(':question_options_attributes') - end - end - end -end \ No newline at end of file diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb deleted file mode 100644 index e02d8a4..0000000 --- a/app/controllers/sections_controller.rb +++ /dev/null @@ -1,92 +0,0 @@ -class SectionsController < ApplicationController - respond_to :html - after_action :verify_authorized - - #create a section - def admin_create - @section = Section.new(params[:section]) - authorize @section - @section.description = params["section-desc"] - @section.modifiable = true - @phase = @section.phase - current_tab = params[:r] || 'all-templates' - if @section.save - @section.phase.template.dirty = true - @section.phase.template.save! - - redirect_to admin_show_phase_path(id: @section.phase_id, r: current_tab, - :section_id => @section.id), notice: success_message(_('section'), _('created')) - else - @edit = (@phase.template.org == current_user.org) - @open = true - @sections = @phase.sections - @section_id = @section.id - @question_id = nil - flash[:alert] = failed_create_error(@section, _('section')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(id: @phase.id, r: current_tab) - end - end - - - #update a section of a template - def admin_update - @section = Section.includes(phase: :template).find(params[:id]) - authorize @section - @section.description = params["section-desc"] - @phase = @section.phase - current_tab = params[:r] || 'all-templates' - if @section.update_attributes(params[:section]) - @section.phase.template.dirty = true - @section.phase.template.save! - - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, r: current_tab), notice: success_message(_('section'), _('saved')) - else - @edit = (@phase.template.org == current_user.org) - @open = true - @sections = @phase.sections - @section_id = @section.id - @question_id = nil - flash[:alert] = failed_update_error(@section, _('section')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to admin_show_phase_path(id: @phase.id, section_id: @section.id, r: current_tab) - end - end - - - #delete a section and questions - def admin_destroy - @section = Section.includes(phase: :template).find(params[:section_id]) - authorize @section - @phase = @section.phase - current_tab = params[:r] || 'all-templates' - if @section.destroy - @phase.template.dirty = true - @phase.template.save! - redirect_to admin_show_phase_path(id: @phase.id, r: current_tab), notice: success_message(_('section'), _('deleted')) - else - @edit = (@phase.template.org == current_user.org) - @open = true - @sections = @phase.sections - @section_id = @section.id - @question_id = nil - - flash[:alert] = failed_destroy_error(@section, _('section')) - if @phase.template.customization_of.present? - @original_org = Template.where(dmptemplate_id: @phase.template.customization_of).first.org - else - @original_org = @phase.template.org - end - redirect_to(admin_show_phase_path(id: @phase.id, r: current_tab)) - end - end - -end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 5376a69..c759af8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -161,7 +161,7 @@ private def org_swap_params - params.require(:user).permit(:org_id, :org_name) + params.require(:superadmin_user).permit(:org_id, :org_name) end ## diff --git a/app/models/annotation.rb b/app/models/annotation.rb index 8ea44dd..ccc1121 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -4,18 +4,14 @@ # Associations belongs_to :org belongs_to :question + has_one :section, through: :question + has_one :phase, through: :question + has_one :template, through: :question ## # I liked type as the name for the enum so overriding inheritance column self.inheritance_column = nil - ## - # Possibly needed for active_admin - # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :org_id, :question_id, :text, :type, - :org, :question, :as => [:default, :admin] - - validates :question, :org, presence: {message: _("can't be blank")} ## @@ -37,4 +33,10 @@ annotation_copy.save! return annotation_copy end -end \ No newline at end of file + + def deep_copy(**options) + copy = self.dup + copy.question_id = options.fetch(:question_id, nil) + return copy + end +end diff --git a/app/models/concerns/exportable_plan.rb b/app/models/concerns/exportable_plan.rb index 020d4fb..ef9dab8 100644 --- a/app/models/concerns/exportable_plan.rb +++ b/app/models/concerns/exportable_plan.rb @@ -15,7 +15,7 @@ if headings hdrs << [_('Section'),_('Question'),_('Answer')] else - csv << _('Answer') + hdrs << [_('Answer')] end csv << hdrs.flatten @@ -26,7 +26,11 @@ answer_text = answer.present? ? answer.text : (unanswered ? 'Not Answered' : '') flds = (hash[:phases].length > 1 ? [phase[:title]] : []) if headings - question_text = (question[:text].length > 1 ? question[:text].join(', ') : question[:text][0]) + if question[:text].is_a? String + question_text = question[:text] + else + question_text = (question[:text].length > 1 ? question[:text].join(', ') : question[:text][0]) + end flds << [ section[:title], sanitize_text(question_text), sanitize_text(answer_text) ] else flds << [ sanitize_text(answer_text) ] diff --git a/app/models/guidance.rb b/app/models/guidance.rb index 4c5bb5c..08a5cb7 100644 --- a/app/models/guidance.rb +++ b/app/models/guidance.rb @@ -54,7 +54,7 @@ # returns all templates belgonging to a specified guidance group # # @param guidance_group [Integer] the integer id for an guidance_group - # @return [Array] list of templates + # @return [Array] list of templates def get_guidance_group_templates? (guidance_group) # DISCUSS - here we have yet another way of finding a specific or group of # an object. Would it make sense to standardise the project by only using diff --git a/app/models/org.rb b/app/models/org.rb index 303cc0d..80cf63d 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -126,7 +126,7 @@ ## # returns all published templates belonging to the organisation # - # @return [Array] published dmptemplates + # @return [Array