diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 28b3fe3..fd991ff 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -56,7 +56,7 @@ } }).find(p_params[:plan_id]) @question = @answer.question - @section = @plan.get_section(@question.section_id) + @section = @plan.sections.find_by(id: @question.section_id) template = @section.phase.template render json: { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d8e5aad..adaa154 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -96,64 +96,53 @@ "#{_('Successfully %{action} your %{object}.') % {object: obj_name, action: action}}" end - # Check whether the string is a valid array of JSON objects - def is_json_array_of_objects?(string) - if string.present? - begin - json = JSON.parse(string) - return (json.is_a?(Array) && json.all?{ |o| o.is_a?(Hash) }) - rescue JSON::ParserError - return false + private + + # Override rails default render action to look for a branded version of a + # template instead of using the default one. If no override exists, the + # default version in ./app/views/[:controller]/[:action] will be used + # + # The path in the app/views/branded/ directory must match the the file it is + # replacing. For example: + # app/views/branded/layouts/_header.html.erb -> app/views/layouts/_header.html.erb + def prepend_view_paths + prepend_view_path "app/views/branded" + end + + def errors_to_s(obj) + if obj.errors.count > 0 + msg = "
" + obj.errors.each do |e,m| + if m.include?('empty') || m.include?('blank') + msg += "#{_(e)} - #{_(m)}
" + else + msg += "'#{obj[e]}' - #{_(m)}
" + end end + msg end end - private - # Override rails default render action to look for a branded version of a - # template instead of using the default one. If no override exists, the - # default version in ./app/views/[:controller]/[:action] will be used - # - # The path in the app/views/branded/ directory must match the the file it is - # replacing. For example: - # app/views/branded/layouts/_header.html.erb -> app/views/layouts/_header.html.erb - def prepend_view_paths - prepend_view_path "app/views/branded" + ## + # Sign out of Shibboleth SP local session too. + # ------------------------------------------------------------- + def after_sign_out_path_for(resource_or_scope) + if Rails.application.config.shibboleth_enabled + return Rails.application.config.shibboleth_logout_url + root_url + super + else + super end + end + # ------------------------------------------------------------- - def errors_to_s(obj) - if obj.errors.count > 0 - msg = "
" - obj.errors.each do |e,m| - if m.include?('empty') || m.include?('blank') - msg += "#{_(e)} - #{_(m)}
" - else - msg += "'#{obj[e]}' - #{_(m)}
" - end - end - msg - end + def from_external_domain? + if request.referer.present? + referer = URI.parse(request.referer) + home = URI.parse(root_url) + referer.host != home.host + else + false end - - ## - # Sign out of Shibboleth SP local session too. - # ------------------------------------------------------------- - def after_sign_out_path_for(resource_or_scope) - if Rails.application.config.shibboleth_enabled - return Rails.application.config.shibboleth_logout_url + root_url - super - else - super - end - end - # ------------------------------------------------------------- - - def from_external_domain? - if request.referer.present? - referer = URI.parse(request.referer) - home = URI.parse(root_url) - referer.host != home.host - else - false - end - end + end end diff --git a/app/controllers/concerns/conditional_user_mailer.rb b/app/controllers/concerns/conditional_user_mailer.rb index aa7622b..d8bc509 100644 --- a/app/controllers/concerns/conditional_user_mailer.rb +++ b/app/controllers/concerns/conditional_user_mailer.rb @@ -1,35 +1,27 @@ -module ConditionalUserMailer - extend ActiveSupport::Concern +# frozen_string_literal: true - # Adds following methods as class methods for the class that include this module - included do - # Executes a given block passed if the recipient user has the preference email key enabled - # @param recipients {User | Enumerable } User object or any object that includes Enumerable class - # @param key {String} - A key (dot notation) whose value is true/false and belongs to prefences.email (see config/branding.yml) - def deliver_if(recipients: [], key:) - raise(ArgumentError, 'key must be String') unless key.is_a?(String) - if block_given? - split_key = key.split('.') - if !recipients.respond_to?(:each) - recipients = Array(recipients) - end - recipients.each do |r| - if r.respond_to?(:get_preferences) - email_hash = r.get_preferences('email') - should_deliver = split_key.reduce(email_hash) do |m,o| - if m.is_a?(Hash) - m[o.to_sym] - else - break - end - end - yield r if should_deliver.is_a?(TrueClass) - end - end - true - else # Block not given - false - end +module ConditionalUserMailer + + # Executes a given block passed if the recipient user has the preference + # email key enabled + # + # @param recipients {User | Enumerable } User object or any object that + # includes Enumerable class + # + # @param key {String} - A key (dot notation) whose value is true/false and + # belongs to prefences.email (see config/branding.yml) + # + # Returns true or false + def deliver_if(recipients: [], key:, &block) + return false unless block_given? + + Array(recipients).each do |recipient| + email_hash = recipient.get_preferences('email').with_indifferent_access + preference_value = !!email_hash.dig(*key.to_s.split('.')) + block.call(recipient) if preference_value end + + true end -end \ No newline at end of file + +end diff --git a/app/controllers/concerns/versionable.rb b/app/controllers/concerns/versionable.rb index 676d4d2..aa633d7 100644 --- a/app/controllers/concerns/versionable.rb +++ b/app/controllers/concerns/versionable.rb @@ -1,106 +1,123 @@ 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) - + ## + # 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 - 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 + elsif obj.is_a?(Template) + template = obj + else + raise ArgumentError, + _('obj should be a Template, Phase, Section, Question, or Annotation') end + + # raises RuntimeError if template is not latest + new_template = Template.find_or_generate_version!(template) + + 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) + unless obj.respond_to?(:template) + raise ArgumentError, + _('obj should be a Phase, Section, Question, or Annotation') + end + + template = obj.template + # raises RuntimeError if template is not latest + new_template = Template.find_or_generate_version!(template) + + 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 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 } + # 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) + unless search_space.respond_to?(:each) + raise ArgumentError, _('The search_space does not respond to each') + end + unless search_space.length > 0 + raise ArgumentError, + _('The search space does not have elements associated') + end + + if obj.is_a?(search_space.first.class) + # object is an instance of Phase, Section or Question + if obj.respond_to?(:number) + return search_space.find{ |search| search.number == obj.number } + # object is an instance of Annotation + elsif obj.respond_to?(:org_id) && obj.respond_to?(:text) + return search_space.find do |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 + + 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/org_admin/plans_controller.rb b/app/controllers/org_admin/plans_controller.rb index b8bd89d..6d1e9a6 100644 --- a/app/controllers/org_admin/plans_controller.rb +++ b/app/controllers/org_admin/plans_controller.rb @@ -4,31 +4,31 @@ def index # Test auth directly and throw Pundit error sincePundit is unaware of namespacing raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? - + vals = Role.access_values_for(:reviewer) @feedback_plans = Plan.joins(:roles).where('roles.user_id = ? and roles.access IN (?)', current_user.id, vals) @plans = current_user.org.plans end - + # GET org_admin/plans/:id/feedback_complete def feedback_complete plan = Plan.find(params[:id]) # Test auth directly and throw Pundit error sincePundit is unaware of namespacing raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? raise Pundit::NotAuthorizedError unless plan.reviewable_by?(current_user.id) - + if plan.complete_feedback(current_user) redirect_to org_admin_plans_path, notice: _('%{plan_owner} has been notified that you have finished providing feedback') % { plan_owner: plan.owner.name(false) } else redirect_to org_admin_plans_path, alert: _('Unable to notify user that you have finished providing feedback.') end end - + # GET /org_admin/download_plans def download_plans # Test auth directly and throw Pundit error sincePundit is unaware of namespacing raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? - + org = current_user.org file_name = org.name.gsub(/ /, "_") header_cols = [ @@ -40,20 +40,20 @@ "#{_('Updated')}", "#{_('Visibility')}" ] - + plans = CSV.generate do |csv| csv << header_cols org.plans.includes(template: :org).order(updated_at: :desc).each do |plan| owner = plan.owner csv << [ - "#{plan.title}", - "#{plan.template.title}", - "#{plan.owner.org.present? ? plan.owner.org.name : ''}", + "#{plan.title}", + "#{plan.template.title}", + "#{plan.owner.org.present? ? plan.owner.org.name : ''}", "#{plan.owner.name(false)}", "#{plan.owner.email}", "#{l(plan.latest_update.to_date, formats: :short)}", - "#{Plan.visibility_message(plan.visibility.to_sym).capitalize}" - ] + "#{Plan::VISIBILITY_MESSAGE[plan.visibility.to_sym].capitalize}" + ] end end diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 9b1a535..fde81df 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -168,7 +168,6 @@ 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, guidance_service: GuidanceService.new(plan) }) @@ -237,9 +236,6 @@ def status @plan = Plan.find(params[:id]) authorize @plan - respond_to do |format| - format.json { render json: @plan.status } - end end def answer @@ -405,25 +401,6 @@ 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 diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb index 913bc2a..6752ef5 100644 --- a/app/controllers/roles_controller.rb +++ b/app/controllers/roles_controller.rb @@ -9,7 +9,7 @@ authorize @role access_level = params[:role][:access_level].to_i - @role.set_access_level(access_level) + @role.access_level = access_level message = '' if params[:user].present? if @role.plan.owner.present? && @role.plan.owner.email == params[:user] @@ -51,7 +51,7 @@ @role = Role.find(params[:id]) authorize @role access_level = params[:role][:access_level].to_i - @role.set_access_level(access_level) + @role.access_level = access_level if @role.update_attributes(role_params) deliver_if(recipients: @role.user, key: 'users.added_as_coowner') do |r| UserMailer.permissions_change_notification(@role, current_user).deliver_now diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a2a6012..ea0077a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,5 @@ module ApplicationHelper - + def resource_name :user end @@ -13,11 +13,6 @@ def devise_mapping @devise_mapping ||= Devise.mappings[:user] end - - # --------------------------------------------------------------------------- - def hash_to_js_json_variable(obj_name, hash) - "".html_safe - end # Determines whether or not the URL path passed matches with the full path (including params) of the last URL requested. # see http://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-fullpath for details @@ -30,10 +25,6 @@ end end - def is_integer?(string) - return string.present? && string.match(/^(\d)+$/) - end - def fingerprinted_asset(name) Rails.env.production? ? "#{name}-#{ASSET_FINGERPRINT}" : name end diff --git a/app/helpers/plans_helper.rb b/app/helpers/plans_helper.rb index 1b9cb6d..b726508 100644 --- a/app/helpers/plans_helper.rb +++ b/app/helpers/plans_helper.rb @@ -1,27 +1,10 @@ module PlansHelper - # Shows whether the user has default, template-default or custom settings - # for the given plan. - # -------------------------------------------------------- - def plan_settings_indicator(plan) - plan_settings = plan.super_settings(:export) - template_settings = plan.template.try(:settings, :export) - - key = if plan_settings.try(:value?) - plan_settings.formatting == template_settings.formatting ? "template_formatting" : "custom_formatting" - elsif template_settings.try(:value?) - "template_formatting" - else - "default_formatting" - end - - content_tag(:small, t("helpers.settings.plans.#{key}")) - end # display the role of the user for a given plan def display_role(role) if role.creator? access = _('Owner') - + else case role.access_level when 3 @@ -48,7 +31,7 @@ return "#{_('Private')}" # Test Plans end end - + def visibility_tooltip(val) case val when 'organisationally_visible' diff --git a/app/models/annotation.rb b/app/models/annotation.rb index b8a6968..5dc5b9d 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -53,6 +53,7 @@ uniqueness: { message: UNIQUENESS_MESSAGE, scope: [:question_id, :org_id] } + # ================= # = Class Methods = # ================= diff --git a/app/models/answer.rb b/app/models/answer.rb index 46d9aa8..22b9a09 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -72,8 +72,7 @@ answer.question_options.each do |opt| answer_copy.question_options << opt end - answer_copy.save! - return answer_copy + answer_copy end # This method helps to decide if an answer option (:radiobuttons, :checkbox, etc ) in form views should be checked or not diff --git a/app/models/concerns/exportable_plan.rb b/app/models/concerns/exportable_plan.rb index f4cec41..37af160 100644 --- a/app/models/concerns/exportable_plan.rb +++ b/app/models/concerns/exportable_plan.rb @@ -1,122 +1,120 @@ module ExportablePlan - extend ActiveSupport::Concern - included do - - def as_pdf(coversheet = false) - prepare(coversheet) - end - - def as_csv(headings = true, unanswered = true) - hash = prepare(false) - - CSV.generate do |csv| - hdrs = (hash[:phases].length > 1 ? [_('Phase')] : []) - if headings - hdrs << [_('Section'),_('Question'),_('Answer')] - else - hdrs << [_('Answer')] - end - - csv << hdrs.flatten - hash[:phases].each do |phase| - phase[:sections].each do |section| - section[:questions].each do |question| - answer = self.answer(question[:id], false) - answer_text = answer.present? ? answer.text : (unanswered ? _('Not Answered') : '') - flds = (hash[:phases].length > 1 ? [phase[:title]] : []) - if headings - 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) ] - end - - csv << flds.flatten - end - end - end - end - end - - private - def prepare(coversheet = false) - hash = coversheet ? prepare_coversheet : {} - template = Template.includes(phases: { sections: {questions: :question_format } }). - joins(phases: { sections: { questions: :question_format } }). - where(id: self.template_id).order('sections.number', 'questions.number').first - - hash[:title] = self.title - hash[:answers] = self.answers - - # add the relevant questions/answers - phases = [] - template.phases.each do |phase| - phs = { title: phase.title, number: phase.number, sections: [] } - phase.sections.each do |section| - sctn = { title: section.title, number: section.number, questions: [] } - section.questions.each do |question| - txt = question.text - sctn[:questions] << { id: question.id, text: txt, format: question.question_format } - end - phs[:sections] << sctn - end - phases << phs - end - hash[:phases] = phases - - record_plan_export(:pdf) - - hash - end - - def prepare_coversheet - hash = {} - # name of owner and any co-owners - attribution = self.owner.present? ? [self.owner.name(false)] : [] - self.roles.administrator.not_creator.each do |role| - attribution << role.user.name(false) - end - hash[:attribution] = attribution - - # Org name of plan owner's org - hash[:affiliation] = self.owner.present? ? self.owner.org.name : '' - - # set the funder name - hash[:funder] = self.funder_name.present? ? self.funder_name : (self.template.org.present? ? self.template.org.name : '') - - # set the template name and customizer name if applicable - hash[:template] = self.template.title - customizer = "" - cust_questions = self.questions.where(modifiable: true).pluck(:id) - # if the template is customized, and has custom answered questions - if self.template.customization_of.present? && Answer.where(plan_id: self.id, question_id: cust_questions).present? - customizer = _(" Customised By: ") + self.template.org.name - end - hash[:customizer] = customizer - hash - end - - def record_plan_export(format) - exported_plan = ExportedPlan.new.tap do |ep| - ep.plan = self - ep.phase_id = self.phases.first.id - ep.format = format - plan_settings = self.settings(:export) - - Settings::Template::DEFAULT_SETTINGS.each do |key, value| - ep.settings(:export).send("#{key}=", plan_settings.send(key)) - end - end - exported_plan.save - end - - def sanitize_text(text) - if (!text.nil?) then ActionView::Base.full_sanitizer.sanitize(text.gsub(/ /i,"")) end - end + def as_pdf(coversheet = false) + prepare(coversheet) end + + def as_csv(headings = true, unanswered = true) + hash = prepare(false) + + CSV.generate do |csv| + hdrs = (hash[:phases].length > 1 ? [_('Phase')] : []) + if headings + hdrs << [_('Section'),_('Question'),_('Answer')] + else + hdrs << [_('Answer')] + end + + csv << hdrs.flatten + hash[:phases].each do |phase| + phase[:sections].each do |section| + section[:questions].each do |question| + answer = self.answer(question[:id], false) + answer_text = answer.present? ? answer.text : (unanswered ? _('Not Answered') : '') + flds = (hash[:phases].length > 1 ? [phase[:title]] : []) + if headings + 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) ] + end + + csv << flds.flatten + end + end + end + end + end + + private + + def prepare(coversheet = false) + hash = coversheet ? prepare_coversheet : {} + template = Template.includes(phases: { sections: {questions: :question_format } }). + joins(phases: { sections: { questions: :question_format } }). + where(id: self.template_id).order('sections.number', 'questions.number').first + + hash[:title] = self.title + hash[:answers] = self.answers + + # add the relevant questions/answers + phases = [] + template.phases.each do |phase| + phs = { title: phase.title, number: phase.number, sections: [] } + phase.sections.each do |section| + sctn = { title: section.title, number: section.number, questions: [] } + section.questions.each do |question| + txt = question.text + sctn[:questions] << { id: question.id, text: txt, format: question.question_format } + end + phs[:sections] << sctn + end + phases << phs + end + hash[:phases] = phases + + record_plan_export(:pdf) + + hash + end + + def prepare_coversheet + hash = {} + # name of owner and any co-owners + attribution = self.owner.present? ? [self.owner.name(false)] : [] + self.roles.administrator.not_creator.each do |role| + attribution << role.user.name(false) + end + hash[:attribution] = attribution + + # Org name of plan owner's org + hash[:affiliation] = self.owner.present? ? self.owner.org.name : '' + + # set the funder name + hash[:funder] = self.funder_name.present? ? self.funder_name : (self.template.org.present? ? self.template.org.name : '') + + # set the template name and customizer name if applicable + hash[:template] = self.template.title + customizer = "" + cust_questions = self.questions.where(modifiable: true).pluck(:id) + # if the template is customized, and has custom answered questions + if self.template.customization_of.present? && Answer.where(plan_id: self.id, question_id: cust_questions).present? + customizer = _(" Customised By: ") + self.template.org.name + end + hash[:customizer] = customizer + hash + end + + def record_plan_export(format) + exported_plan = ExportedPlan.new.tap do |ep| + ep.plan = self + ep.phase_id = self.phases.first.id + ep.format = format + plan_settings = self.settings(:export) + + Settings::Template::DEFAULT_SETTINGS.each do |key, value| + ep.settings(:export).send("#{key}=", plan_settings.send(key)) + end + end + exported_plan.save + end + + def sanitize_text(text) + ActionView::Base.full_sanitizer.sanitize(text.to_s.gsub(/ /i,"")) + end + end diff --git a/app/models/concerns/json_link_validator.rb b/app/models/concerns/json_link_validator.rb index 8315a7d..739174c 100644 --- a/app/models/concerns/json_link_validator.rb +++ b/app/models/concerns/json_link_validator.rb @@ -1,32 +1,15 @@ module JSONLinkValidator - extend ActiveSupport::Concern - included do - # Parses a stringified JSON according to validate_links (e.g. [{ link: String, text: String}, ...]) - # param {String} the stringified JSON value - # Returns an Array of hashes after decoding/validating the stringified JSON passed, otherwise nil - def parse_links(value) - return nil unless value.is_a?(String) - begin - parsed_value = JSON.parse(value) - return valid_links?(parsed_value) ? parsed_value : nil - rescue JSON::ParserError - nil + # Validates whether or not the value passed is conforming to + # [{ link: String, text: String}, ...] + def valid_links?(value) + if value.is_a?(Array) + r = value.all? do |o| + o.is_a?(Hash) && o.key?('link') && o.key?('text') && + o['link'].is_a?(String) && o['text'].is_a?(String) end + return r end - # Validates whether or not the value passed is conforming to [{ link: String, text: String}, ...] - def valid_links?(value) - if value.is_a?(Array) - r = value.all? do |o| - o.is_a?(Hash) && - o.has_key?('link') && - o.has_key?('text') && - o['link'].is_a?(String) && - o['text'].is_a?(String) - end - return r - end - false - end + false end end \ No newline at end of file diff --git a/app/models/guidance.rb b/app/models/guidance.rb index 5ad8950..4258326 100644 --- a/app/models/guidance.rb +++ b/app/models/guidance.rb @@ -1,3 +1,7 @@ +# Guidance provides information from organisations to Users, helping them when +# answering questions. (e.g. "Here's how to think about your data +# protection responsibilities...") +# # == Schema Information # # Table name: guidances @@ -26,8 +30,6 @@ # [+Created:+] 07/07/2014 # [+Copyright:+] Digital Curation Centre and California Digital Library - - class Guidance < ActiveRecord::Base include GlobalHelpers include ValidationMessages @@ -36,12 +38,10 @@ # ================ # = Associations = # ================ - belongs_to :guidance_group - has_and_belongs_to_many :themes, join_table: "themes_in_guidance" - # EVALUATE CLASS AND INSTANCE METHODS BELOW - # - # What do they do? do they do it efficiently, and do we need them? + belongs_to :guidance_group + + has_and_belongs_to_many :themes, join_table: "themes_in_guidance" # =============== @@ -62,37 +62,15 @@ scope :search, -> (term) { search_pattern = "%#{term}%" - joins(:guidance_group).where("guidances.text LIKE ? OR guidance_groups.name LIKE ?", search_pattern, search_pattern) + joins(:guidance_group) + .where("guidances.text LIKE ? OR guidance_groups.name LIKE ?", + search_pattern, + search_pattern) } - ## - # Determine if a guidance is in a group which belongs to a specified organisation - # - # @param org_id [Integer] the integer id for an organisation - # @return [Boolean] true if this guidance is in a group belonging to the specified organisation, false otherwise - def in_group_belonging_to?(org_id) - unless guidance_group.nil? - if guidance_group.org.id == org_id - return true - end - end - return false - end - ## - # 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 - 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 - # either finders or where, or alteast the same syntax within the where statement. - # Also why is this a ? method... it dosent return a boolean - # Additionally, shouldnt this be a function of guidance group, not guidance? - # and finally, it should be a self.method, as it dosent care about the guidance it's acting on - templates = guidancegroups.where("guidance_group_id (?)", guidance_group.id).template - return templates - end + # ================= + # = Class methods = + # ================= ## # Returns whether or not a given user can view a given guidance @@ -103,7 +81,8 @@ # # @param id [Integer] the integer id for a guidance # @param user [User] a user object - # @return [Boolean] true if the specified user can view the specified guidance, false otherwise + # @return [Boolean] true if the specified user can view the specified + # guidance, false otherwise def self.can_view?(user, id) guidance = Guidance.find_by(id: id) viewable = false @@ -114,7 +93,8 @@ if guidance.guidance_group.org == user.org viewable = true end - # guidance groups are viewable if they are owned by the Managing Curation Center + # guidance groups are viewable if they are owned by the Managing + # Curation Center if Org.managing_orgs.include?(guidance.guidance_group.org) viewable = true end @@ -139,16 +119,38 @@ # @param user [User] a user object # @return [Array] a list of all "viewable" guidances to a user def self.all_viewable(user) - managing_groups = Org.includes(guidance_groups: :guidances).managing_orgs.collect{|o| o.guidance_groups} + managing_groups = Org.includes(guidance_groups: :guidances) + .managing_orgs.collect{|o| o.guidance_groups} # find all groups owned by a Funder organisation - funder_groups = Org.includes(guidance_groups: :guidances).funder.collect{|org| org.guidance_groups} + funder_groups = Org.includes(guidance_groups: :guidances) + .funder.collect{|org| org.guidance_groups} # find all groups owned by any of the user's organisations organisation_groups = user.org.guidance_groups # find all guidances belonging to any of the viewable groups - all_viewable_groups = (managing_groups + funder_groups + organisation_groups).flatten - all_viewable_guidances = all_viewable_groups.collect{|group| group.guidances} + all_viewable_groups = (managing_groups + + funder_groups + + organisation_groups).flatten + all_viewable_guidances = all_viewable_groups.collect do |group| + group.guidances + end # pass the list of viewable guidances to the view return all_viewable_guidances.flatten end + + ## + # Determine if a guidance is in a group which belongs to a specified + # organisation + # + # @param org_id [Integer] the integer id for an organisation + # @return [Boolean] true if this guidance is in a group belonging to the + # specified organisation, false otherwise + def in_group_belonging_to?(org_id) + unless guidance_group.nil? + if guidance_group.org.id == org_id + return true + end + end + return false + end end diff --git a/app/models/guidance_group.rb b/app/models/guidance_group.rb index f9520a2..db9888a 100644 --- a/app/models/guidance_group.rb +++ b/app/models/guidance_group.rb @@ -1,3 +1,6 @@ +# Set of Guidances that pertain to a certain category of Users (e.g. Maths +# department, vs Biology department) +# # == Schema Information # # Table name: guidance_groups @@ -18,20 +21,20 @@ # # fk_rails_... (org_id => orgs.id) # - class GuidanceGroup < ActiveRecord::Base include GlobalHelpers include ValidationValues include ValidationMessages - ## - # Associations - belongs_to :org - has_many :guidances, dependent: :destroy - has_and_belongs_to_many :plans, join_table: :plans_guidance_groups - # depricated but needed for migration "single_group_for_guidance" - # has_and_belongs_to_many :guidances, join_table: "guidance_in_group" + # ================ + # = Associations = + # ================ + belongs_to :org + + has_many :guidances, dependent: :destroy + + has_and_belongs_to_many :plans, join_table: :plans_guidance_groups # =============== # = Validations = @@ -54,48 +57,18 @@ # What do they do? do they do it efficiently, and do we need them? # Retrieves every guidance group associated to an org - scope :by_org, -> (org) { - where(org_id: org.id) - } - scope :search, -> (term) { + scope :by_org, ->(org) { where(org_id: org.id) } + + scope :search, lambda { |term| search_pattern = "%#{term}%" where("name LIKE ?", search_pattern) } - ## - # Converts the current guidance group to a string containing the display name. - # If it's organisation has no other guidance groups, then the name is simply - # the name of the parent organisation, otherwise it returns the name of the - # organisation followed by the name of the guidance group. - # - # @return [String] the display name for the guidance group - def display_name - if org.guidance_groups.count > 1 - return "#{org.name}: #{name}" - else - return org.name - end - end + scope :published, -> { where(published: true) } - ## - # Returns the list of all guidance groups not coming from the given organisations - # - # @param excluded_orgs [Array] a list of organisations to exclude in the result - # @return [Array] a list of guidance groups - def self.guidance_groups_excluding(excluded_orgs) - excluded_org_ids = Array.new - - if excluded_orgs.is_a?(Array) - excluded_orgs.each do |org| - excluded_org_ids << org.id - end - else - excluded_org_ids << excluded_orgs - end - - return_orgs = GuidanceGroup.where("org_id NOT IN (?)", excluded_org_ids) - return return_orgs - end + # ================= + # = Class methods = + # ================= ## # Returns whether or not a given user can view a given guidance group @@ -106,48 +79,50 @@ # # @param id [Integer] the integer id for a guidance group # @param user [User] a user object - # @return [Boolean] true if the specified user can view the specified guidance group, false otherwise + # @return [Boolean] true if the specified user can view the specified + # guidance group, false otherwise def self.can_view?(user, guidance_group) viewable = false # groups are viewable if they are owned by any of the user's organisations - if guidance_group.org == user.org - viewable = true - end + viewable = true if guidance_group.org == user.org # groups are viewable if they are owned by the managing curation center Org.managing_orgs.each do |managing_group| - if guidance_group.org.id == managing_group.id - viewable = true - end + viewable = true if guidance_group.org.id == managing_group.id end # groups are viewable if they are owned by a funder - if guidance_group.org.funder? - viewable = true - end + viewable = true if guidance_group.org.funder? - return viewable + viewable end - ## - # Returns a list of all guidance groups which a specified user can view - # we define guidance groups viewable to a user by those owned by: - # the Managing Curation Center - # a funder organisation - # an organisation, of which the user is a member - # - # @param user [User] a user object - # @return [Array] a list of all "viewable" guidance groups to a user + ## + # Returns a list of all guidance groups which a specified user can view + # we define guidance groups viewable to a user by those owned by: + # the Managing Curation Center + # a funder organisation + # an organisation, of which the user is a member + # + # @param user [User] a user object + # @return [Array] a list of all "viewable" guidance groups to + # a user def self.all_viewable(user) # first find all groups owned by the Managing Curation Center - managing_org_groups = Org.includes(guidance_groups: [guidances: :themes]).managing_orgs.collect{|org| org.guidance_groups} + managing_org_groups = Org.includes(guidance_groups: [guidances: :themes]) + .managing_orgs.collect(&:guidance_groups) # find all groups owned by a Funder organisation - funder_groups = Org.includes(:guidance_groups).funder.collect{|org| org.guidance_groups} + funder_groups = Org.includes(:guidance_groups) + .funder + .collect(&:guidance_groups) organisation_groups = [user.org.guidance_groups] - # pass this organisation guidance groups to the view with respond_with @all_viewable_groups - all_viewable_groups = managing_org_groups + funder_groups + organisation_groups + # pass this organisation guidance groups to the view with respond_with + # @all_viewable_groups + all_viewable_groups = managing_org_groups + + funder_groups + + organisation_groups all_viewable_groups = all_viewable_groups.flatten.uniq - return all_viewable_groups + all_viewable_groups end end diff --git a/app/models/notification.rb b/app/models/notification.rb index cfbe0cb..b5a6962 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -71,19 +71,6 @@ # If no user is given, currently logged in user (if any) is the default # @return [Boolean] is the Notification acknowledged ? def acknowledged?(user) - users.include?(user) if user.present? && dismissable? - end - - private - - # Validate Notification dates - def valid_dates - return false if starts_at.blank? || expires_at.blank? - errors.add(:starts_at, _('Should be today or later')) if starts_at < Date.today - errors.add(:expires_at, _('Should be tomorrow or later')) if expires_at < Date.tomorrow - if starts_at > expires_at - errors.add(:starts_at, _('Should be before expiration date')) - errors.add(:expires_at, _('Should be after start date')) - end + dismissable? && user.present? && users.include?(user) end end diff --git a/app/models/org.rb b/app/models/org.rb index 443c0e3..36e2bd0 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -122,7 +122,9 @@ column: 'org_type' # Predefined queries for retrieving the managain organisation and funders - scope :managing_orgs, -> { where(abbreviation: Rails.configuration.branding[:organisation][:abbreviation]) } + scope :managing_orgs, -> do + where(abbreviation: Branding.fetch(:organisation, :abbreviation)) + end scope :search, -> (term) { search_pattern = "%#{term}%" @@ -194,17 +196,8 @@ # returns all published templates belonging to the organisation # # @return [Array