diff --git a/Gemfile b/Gemfile index b5e46c3..2b41610 100644 --- a/Gemfile +++ b/Gemfile @@ -15,8 +15,8 @@ # ------------------------------------------------ # DATABASE/SERVER -gem 'pg' gem 'mysql2', '~> 0.3.18' +gem 'pg' gem 'flag_shih_tzu' # Allows for bitfields in activereccord # ------------------------------------------------ @@ -29,18 +29,10 @@ gem 'jbuilder' # ------------------------------------------------ -# CLONE ACTIVERECORD MODELS AND ASSOCIATIONS -gem 'amoeba' - -# ------------------------------------------------ # SLUGS/PERMALINKS gem 'friendly_id' # ------------------------------------------------ -# BIT FIELDS -gem 'flag_shih_tzu' - -# ------------------------------------------------ # SUPER ADMIN SECTION gem 'activeadmin', github: 'activeadmin' @@ -87,6 +79,7 @@ # ------------------------------------------------ # INTERNATIONALIZATION gem "i18n-js", ">= 3.0.0.rc11" #damodar added TODO: explain +gem 'gettext_i18n_rails', '~> 1.8' # ------------------------------------------------ # API diff --git a/Gemfile.lock b/Gemfile.lock index 8359a7c..d430a3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,8 +64,6 @@ thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.4.0) - amoeba (3.0.0) - activerecord (>= 3.2.6) ansi (1.5.0) arbre (1.1.1) activesupport (>= 3.0.0) @@ -360,7 +358,6 @@ DEPENDENCIES activeadmin! - amoeba better_errors binding_of_caller byebug diff --git a/app/admin/organisation_type.rb b/app/admin/organisation_type.rb deleted file mode 100644 index 4575f54..0000000 --- a/app/admin/organisation_type.rb +++ /dev/null @@ -1,55 +0,0 @@ -# [+Project:+] DMPRoadmap -# [+Description:+] -# -# [+Created:+] 03/09/2014 -# [+Copyright:+] Digital Curation Centre and University of California Curation Center - -ActiveAdmin.register OrganisationType do - permit_params :organisation_id, :name - - menu :priority => 4, :label => proc{I18n.t('admin.org_type')}, :parent => "Organisations management" - - index do - column I18n.t('admin.title'), :sortable => :name do |ggn| - link_to ggn.name, [:admin, ggn] - end - column I18n.t('admin.desc'), :description do |descr| - if !descr.description.nil? then - descr.description.html_safe - end - end - - actions - end - - - #show organisation type details - show do - attributes_table do - row :name - row :description do |descr| - if !descr.description.nil? then - descr.description.html_safe - end - end - row :created_at - row :updated_at - end - end - - #organisations sidebar - sidebar I18n.t('admin.orgs'), :only => :show, :if => proc { organisation_type.organisations.count >= 1} do - table_for organisation_type.organisations.order("name") do |org_list| - column I18n.t('admin.org_title'), :sortable => :name do |ggn| - link_to ggn.name, [:admin, ggn] - end - end - end - - controller do - def permitted_params - params.permit! - end - end - -end diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index e16f81b..8ef2500 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -3,43 +3,48 @@ respond_to :html ## - # POST /answers - # current implimentation creates a new answer each time one is submitted - # - # Probably should rename from from create to update - # Maybe better to just update the existing answer rather than generate a new - # one. Especiall since comments connect to answers - def create + # PUT/PATCH /[:locale]/answer/[:id] + def update # create a new answer based off the passed params - @answer = Answer.new(params[:answer]) - authorize @answer - # find the prevous answer to this question for this plan (created_at: "DESC").first - old_answer = @answer.plan.answer(@answer.question_id, false) - proceed = false - # confused why we are passing the text through like this if we have clearly passed the answer's other attr through plainly - # We can re-name it as it's defined in app/views/plans/_answer_form.html.erb - @answer.text = params["answer-text-#{@answer.question_id}".to_sym] - if (old_answer.nil? && @answer.text != "") || ((!old_answer.nil?) && (old_answer.text != @answer.text)) then - proceed = true - end - # Is this validation necissary? - if (@answer.question.question_format.title == I18n.t("helpers.checkbox") || - @answer.question.question_format.title == I18n.t("helpers.multi_select_box") || - @answer.question.question_format.title == I18n.t("helpers.radio_buttons") || - @answer.question.question_format.title == I18n.t("helpers.dropdown")) then - if (old_answer.nil? && @answer.option_ids.count > 0) || ((!old_answer.nil?) && (old_answer.option_ids - @answer.option_ids).count != 0 && (@answer.option_ids - old_answer.option_ids).count != 0) then - proceed = true - end - end - if proceed + ans_params = params[:answer] + plan_id = ans_params[:plan_id] + user_id = ans_params[:user_id] + question_id = ans_params[:question_id] + @answer = Answer.find_by( + plan_id: plan_id, + user_id: user_id, + question_id: question_id) + if @answer.nil? + @answer = Answer.new(params[:answer]) + end + + authorize @answer + +puts params.inspect + + @answer.text = params["answer-text-#{@answer.question_id}".to_sym] + + #TODO: check for optimistic locking + + # Is this validation necessary? +# if (@answer.question.question_format.title == I18n.t("helpers.checkbox") || +# @answer.question.question_format.title == I18n.t("helpers.multi_select_box") || +# @answer.question.question_format.title == I18n.t("helpers.radio_buttons") || +# @answer.question.question_format.title == I18n.t("helpers.dropdown")) then +# if (old_answer.nil? && @answer.option_ids.count > 0) || ((!old_answer.nil?) && (old_answer.option_ids - @answer.option_ids).count != 0 && (@answer.option_ids - old_answer.option_ids).count != 0) then +# proceed = true +# end +# end + +# if proceed if @answer.save redirect_to :back, status: :found, notice: I18n.t('helpers.project.answer_recorded') else redirect_to :back, notice: I18n.t('helpers.project.answer_error') end - else - redirect_to :back, notice: I18n.t('helpers.project.answer_no_change') - end +# else +# redirect_to :back, notice: I18n.t('helpers.project.answer_no_change') +# end end -end \ No newline at end of file +end diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index b36bfa8..631983e 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -121,7 +121,7 @@ end def has_auth (auth_type) - auth = false + #auth = false # not sure if initial if is necissary, but it works with it there... refactor later? # if !TokenPermission.where(api_token: @token).nil? # TokenPermission.where(api_token: @token).find_each do |permission| @@ -131,13 +131,16 @@ # end # end # end - OrgTokenPermission.where(org_id: @user.org_id).find_each do |org_token_permission| - logger.debug "#{org_token_permission.token_permission_type.token_type}" - if org_token_permission.token_permission_type.token_type == auth_type - auth= true - end - end - return auth + + #OrgTokenPermission.where(org_id: @user.org_id).find_each do |org_token_permission| + # logger.debug "#{org_token_permission.token_permission_type.token_type}" + # if org_token_permission.token_permission_type.token_type == auth_type + # auth= true + # end + #end + #return auth + tpt = TokenPermissionType.find_by(token_type: auth) + org.token_permission_types.include?(tpt) end end diff --git a/app/controllers/api/v0/plans_controller.rb b/app/controllers/api/v0/plans_controller.rb index ad76c3d..c96d174 100644 --- a/app/controllers/api/v0/plans_controller.rb +++ b/app/controllers/api/v0/plans_controller.rb @@ -21,20 +21,20 @@ if has_auth(constant("api_endpoint_types.plans")) #params[:organization_id] = Org.where(name: params[:template][:organization]) # find_by returns nil if none found, find_by! raises an ActiveRecord error - organization = Org.find_by name: params[:template][:organisation] + org = Org.find_by name: params[:template][:organisation] # if organization exists - if !organization.nil? + if !org.nil? # if organization is funder - if organization.organisation_type == (OrganisationType.find_by(name: constant("organisation_types.funder"))) + if org.funder? # if organization has only 1 template - if organization.dmptemplates.length == 1 + if org.templates.length == 1 # set template id - dmptemplate = organization.dmptemplates.first + template = org.templates.first # else if params.template.name specified && params.template.name == one of organization's tempates - elsif !organization.dmptemplates.find_by title: params[:template][:name].nil? + elsif !org.templates.find_by title: params[:template][:name].nil? # set template id - dmptemplate = organization.templates.find_by title: params[:template][:name] + template = org.templates.find_by title: params[:template][:name] # else error: organization has more than one template and template name unspecified else render json: I18n.t("api.org_multiple_templates"), status: 400 and return @@ -73,11 +73,11 @@ end # create new project with specified parameters - @project = Project.new + @project = Plan.new @project.title = params[:project][:title] - @project.dmptemplate = dmptemplate + @project.template = template @project.slug = params[:project][:title] - @project.organisation = @user.organisations.first + #@project.organisation = @user.organisations.first @project.assign_creator(user.id) @project.guidance_groups = all_groups diff --git a/app/controllers/api/v0/statistics_controller.rb b/app/controllers/api/v0/statistics_controller.rb index f893bc5..a09bec3 100644 --- a/app/controllers/api/v0/statistics_controller.rb +++ b/app/controllers/api/v0/statistics_controller.rb @@ -9,7 +9,7 @@ # users are scoped to the organisation of the user initiating the call def users_joined if has_auth(constant("token_permission_types.statistics")) - users = restrict_date_range(@user.organisations.first.users) + users = restrict_date_range(@user.org.users) confirmed_users = [] users.each do |user| unless user.confirmed_at.blank? @@ -31,8 +31,8 @@ def using_template if has_auth(constant("token_permission_types.statistics")) template = Dmptemplate.find(params[:id]) - if template.organisation == @user.organisations.first - @template_count = restrict_date_range(template.projects).count + if template.org == @user.org + @template_count = restrict_date_range(template.plans).count respond_with @template_count else #no auth to view statistics for this template @@ -50,10 +50,10 @@ def plans_by_template if has_auth(constant("token_permission_types.statistics")) @org_projects = [] - @user.organisations.first.users.each do |user| - user.projects.each do |project| - unless @org_projects.include? project - @org_projects += [project] + @user.org.users.each do |user| + user.plans.each do |plan| + unless @org_projects.include? plan + @org_projects += [plan] end end end @@ -72,10 +72,10 @@ def plans if has_auth(constant("token_permission_types.statistics")) @org_projects = [] - @user.organisations.first.users.each do |user| - user.projects.each do |project| - unless @org_projects.include? project - @org_projects += [project] + @user.org.users.each do |user| + user.plans.each do |plan| + unless @org_projects.include? plan + @org_projects += [plan] end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6564cb5..a02811c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -17,29 +17,21 @@ redirect_to root_url, alert: I18n.t('unauthorized') end - before_filter :set_locale + before_filter :set_gettext_locale after_filter :store_location - def set_locale - # parameter from url takes precedence - # check if locale is defined - if params[:locale] # and I18n.available_locales.include? params[:locale] # throw an error if not available - # if locales data is present in the parameter from url use it - I18n.locale = params[:locale] - + def set_gettext_locale + if params[:locale] and FastGettext.default_available_locales.include?(params[:locale]) + FastGettext.locale = params[:locale] elsif user_signed_in? and !current_user[:language_id].nil? - I18n.locale = Language.find_by_id(current_user[:language_id]).abbreviation - # if user has set preferred language use it - + FastGettext.locale = Language.find_by_id(current_user[:language_id]).abbreviation #Relies on successful db call elsif user_signed_in? and current_user.org.present? and !current_user.org[:language_id].nil? - I18n.locale = Language.find_by_id(current_user.org[:language_id]).abbreviation - # use user's organization language, keep in mine the "OTHER ORG" edge case which should use default language - + FastGettext.locale = Language.find_by_id(current_user.org[:language_id]).abbreviation #Relies on successful db call else - # just use the default language, line can be commented out, included just for clarity - I18n.locale = I18n.default_locale + FastGettext.locale = FastGettext.default_locale end + puts 'FastGettext.locale = '+FastGettext.locale end # Added setting for passing local params across pages diff --git a/app/controllers/guidance_groups_controller.rb b/app/controllers/guidance_groups_controller.rb index da3e685..3729dd7 100644 --- a/app/controllers/guidance_groups_controller.rb +++ b/app/controllers/guidance_groups_controller.rb @@ -59,7 +59,7 @@ def admin_update_publish @guidance_group = GuidanceGroup.find(params[:id]) authorize @guidance_group - @guidance_group.organisation_id = current_user.organisation_id + @guidance_group.org.id = current_user.org.id @guidance_group.published = true if @guidance_group.update_attributes(params[:guidance_group]) diff --git a/app/controllers/guidances_controller.rb b/app/controllers/guidances_controller.rb index 81ea8c9..75c0808 100644 --- a/app/controllers/guidances_controller.rb +++ b/app/controllers/guidances_controller.rb @@ -20,7 +20,11 @@ def admin_new @guidance = Guidance.new authorize @guidance - @templates = Template.funders_and_own_templates(current_user.org_id) + + #@templates = Template.funders_and_own_templates(current_user.org_id) + # Replacing weird accessor on Template + @templates = (Org.funders.collect{|o| o.templates } + current_user.org.templates).flatten + @phases = nil @templates.includes(:phases).each do |template| if @phases.nil? then diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 713dda5..a311c09 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -14,7 +14,7 @@ if name.blank? redirect_to edit_user_registration_path else - redirect_to projects_url + redirect_to plans_url end end end diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index 9ebd6d3..8a400f6 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -5,19 +5,34 @@ ## # POST /notes def create - @note = Note.new(params[:new_note]) - @note.text = params["#{params[:new_note][:question_id]}new_note_text"] - @note.question_id = params[:new_note][:question_id] - @note.user_id = params[:new_note][:user_id] - @note.plan_id = params[:new_note][:plan_id] + @note = Note.new + user_id = params[:new_note][:user_id] + @note.user_id = user_id + + answer_id = params[:new_note][:answer_id] + question_id = params[:new_note][:question_id] + plan_id = params[:new_note][:plan_id] + if answer_id.present? + answer = Answer.find(@note.answer_id) + else + answer = Answer.new + answer.plan_id = plan_id + answer.question_id = question_id + answer.user_id = user_id + answer.save! + end + + @note.answer= answer + @note.text = params["#{params[:new_note][:answer_id]}new_note_text"] + authorize @note - @plan = Plan.find(@note.plan_id) - @project = Project.find(@plan.project_id) + @plan = answer.plan + @phase = answer.question.section.phase if @note.save - session[:question_id_notes] = @note.question_id - redirect_to edit_project_plan_path(@project, @plan), status: :found, notice: I18n.t("helpers.comments.note_created") + session[:question_id_notes] = answer.question_id + redirect_to edit_plan_phase_path(@plan, @phase), status: :found, notice: I18n.t("helpers.comments.note_created") end end diff --git a/app/controllers/phases_controller.rb b/app/controllers/phases_controller.rb new file mode 100644 index 0000000..b65f33f --- /dev/null +++ b/app/controllers/phases_controller.rb @@ -0,0 +1,55 @@ +class PhasesController < ApplicationController + require 'pp' + + after_action :verify_authorized + + TEXTAREA = QuestionFormat.where(title: "Text area").first.id + TEXTFIELD = QuestionFormat.where(title: "Text field").first.id + RADIO = QuestionFormat.where(title: "Radio buttons").first.id + CHECKBOX = QuestionFormat.where(title: "Check box").first.id + DROPDOWN = QuestionFormat.where(title: "Dropdown").first.id + MULTI = QuestionFormat.where(title: "Multi select box").first.id + + # GET /plans/PLANID/phases/PHASEID/edit + def edit + + @textarea = TEXTAREA + @textfield = TEXTFIELD + @radio = RADIO + @checkbox = CHECKBOX + @dropdown = DROPDOWN + @multi = MULTI + + @plan = Plan.find(params[:plan_id]) + authorize @plan + + @plan_data = @plan.to_hash + + phase_id = params[:id].to_i + @phase = Phase.find(phase_id) + @phase_data = @plan_data["template"]["phases"].select {|p| p["id"] == phase_id}.first + + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + end + + end + + + # GET /plans/PLANID/phases/PHASEID/status.json + def status + @plan = Plan.find(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 + + +end diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index ba1f3ab..edae8f5 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -1,208 +1,479 @@ class PlansController < ApplicationController - #Uncomment the line below in order to add authentication to this page - users without permission will not be able to add new plans - #load_and_authorize_resource + require 'pp' + #Uncomment the line below in order to add authentication to this page - users without permission will not be able to add new plans + #load_and_authorize_resource + # + before_filter :get_plan_list_columns, only: %i( index ) after_action :verify_authorized - - # GET /plans/1/edit - def edit - @plan = Plan.find(params[:id]) + TEXTAREA = QuestionFormat.where(title: "Text area").first.id + TEXTFIELD = QuestionFormat.where(title: "Text field").first.id + RADIO = QuestionFormat.where(title: "Radio buttons").first.id + CHECKBOX = QuestionFormat.where(title: "Check box").first.id + DROPDOWN = QuestionFormat.where(title: "Dropdown").first.id + MULTI = QuestionFormat.where(title: "Multi select box").first.id + + def index + authorize Plan + @plans = current_user.plans + end + + + + # GET /plans/new + def new + if user_signed_in? then + @plan = Plan.new + authorize @plan + @funders = Org.funders.all + + respond_to do |format| + format.html # new.html.erb + end + else + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + end + end + + + def create + if user_signed_in? then + @plan = Plan.new + authorize @plan + @plan.save + + funder_id = params[:plan][:funder_id] + if !funder_id.blank? + # get all funder @templates + funder = Org.find(params[:plan][:funder_id]) + @templates = get_most_recent( funder.templates.where("published = ?", true).all ) + + orgtemplates = current_user.org.templates.all + replacements = [] + + # replace any that are customised by the org + orgtemplates.each do |orgt| + base_template = orgt.customization_of + @templates.delete(base_template) + replacements << orgt + end + @templates + replacements + + else + # get all org @templates which are not customisations + @templates = current_user.org.templates.where(customization_of: nil) + + # if none of these get the basic dcc template + if @templates.blank? + @templates = Template.find_by_is_default(true) + end + end + + # if we have more than one template then back to the user + # using the 'create' template + # to choose otherwise just create the plan + # and go to the plan/show template + if @templates.length > 1 + return + end + + @plan.template = @templates[0] + + @plan.principal_investigator = current_user.name + + @plan.title = I18n.t('helpers.project.my_project_name')+' ('+@plan.template.title+')' + + @plan.assign_creator(current_user.id) + + @plan.set_possible_guidance_groups + + @selected_guidance_groups = @plan.guidance_groups.map{ |pgg| [pgg.name, pgg.id, :checked => false] } + @selected_guidance_groups.sort! + + respond_to do |format| + if @plan.save + #format.html { redirect_to({:action => "show", :id => @plan.slug, :show_form => "yes"}, {:notice => I18n.t('helpers.project.success')}) } + format.html { redirect_to({:action => "show", :id => @plan.id, :editing => true }, {:notice => I18n.t('helpers.project.success')}) } + else + @error = "Something went wrong" + format.html { render action: "new" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + + + # GET /plans/show + def show + @plan = Plan.find(params[:id]) authorize @plan + + @plan_data = @plan.to_hash + + @editing = params[:editing] && @plan.administerable_by?(current_user.id) + @selected_guidance_groups = [] + all_guidance_groups = @plan_data["plan_guidance_groups"] + @selected_guidance_groups = all_guidance_groups.map{ |pgg| [ pgg["guidance_group"]["name"], pgg["guidance_group"]["id"], :checked => pgg["selected"] ] } + @selected_guidance_groups.sort! + + if user_signed_in? && @plan.readable_by?(current_user.id) then + respond_to do |format| + format.html # show.html.erb + end + elsif user_signed_in? then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + else + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + end + end + + + + # we can go into this with the user able to edit or not able to edit + # the same edit form gets rendered but then different partials get used + # to render the answers depending on whether it is readonly or not + # + # we may or may not have a phase param. + # if we have none then we are editing/displaying the plan details + # if we have a phase then we are editing that phase. + # + # GET /plans/1/edit + def edit + @textarea = TEXTAREA + @textfield = TEXTFIELD + @radio = RADIO + @checkbox = CHECKBOX + @dropdown = DROPDOWN + @multi = MULTI + + @plan = Plan.find(params[:id]) + + @phase = nil + if params[:phase] + @phase = Phase.find(params[:phase]) + end + + authorize @plan + @readonly = @plan.editable_by?(current_user.id) if !user_signed_in? then respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - elsif !@plan.readable_by(current_user.id) then - respond_to do |format| - format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } - end - end - end + format.html { redirect_to edit_user_registration_path } + end + elsif !@plan.readable_by?(current_user.id) then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end + end - # PUT /plans/1 - # PUT /plans/1.json - def update - @plan = Plan.find(params[:id]) + # PUT /plans/1 + # PUT /plans/1.json + def update + @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.editable_by(current_user.id) then - respond_to do |format| - if @plan.update_attributes(params[:plan]) - format.html { redirect_to @plan, notice: I18n.t('helpers.project.success_update') } - format.json { head :no_content } - else - format.html { render action: "edit" } - end - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + if user_signed_in? && @plan.editable_by?(current_user.id) then + respond_to do |format| + if @plan.update_attributes(params[:plan]) + format.html { redirect_to @plan, :editing => false, notice: I18n.t('helpers.project.success_update') } + format.json { head :no_content } + else + format.html { render action: "edit" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + + + def update_guidance_choices + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.editable_by?(current_user.id) then + guidance_ids = params[:plan][:plan_guidance_group_ids] + @plan.plan_guidance_groups.each do |pgg| + pgg.selected = guidance_ids.include?(pgg.guidance_group_id.to_s) + pgg.save! + end + @plan.save! + + respond_to do |format| + format.json { head :no_content } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + def share + @plan = Plan.find(params[:id]) + authorize @plan + @plan_data = @plan.to_hash + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + elsif !@plan.editable_by?(current_user.id) then + respond_to do |format| + format.html { redirect_to plans_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end + end + + + def destroy + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.editable_by?(current_user.id) then + @plan.destroy + + respond_to do |format| + format.html { redirect_to plans_url } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end # GET /status/1.json - # only returns json, why is this here? + # only returns json, why is this here? def status - @plan = Plan.find(params[: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 - - def section_answers - @plan = Plan.find(params[:id]) - authorize @plan - if user_signed_in? && @plan.readable_by(current_user.id) then - respond_to do |format| - format.json { render json: @plan.section_answers(params[:section_id]) } - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end - - def locked - @plan = Plan.find(params[:id]) - authorize @plan - if !@plan.nil? && user_signed_in? && @plan.readable_by(current_user.id) then - respond_to do |format| - format.json { render json: @plan.locked(params[:section_id],current_user.id) } - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end - - def delete_recent_locks - @plan = Plan.find(params[:id]) + @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.editable_by(current_user.id) then - respond_to do |format| - if @plan.delete_recent_locks(current_user.id) - format.html { render action: "edit" } - else - format.html { render action: "edit" } - end - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + 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 - def unlock_all_sections - @plan = Plan.find(params[:id]) + def section_answers + @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.editable_by(current_user.id) then - respond_to do |format| - if @plan.unlock_all_sections(current_user.id) - format.html { render action: "edit" } - else - format.html { render action: "edit" } - end - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + if user_signed_in? && @plan.readable_by(current_user.id) then + respond_to do |format| + format.json { render json: @plan.section_answers(params[:section_id]) } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end - def lock_section - @plan = Plan.find(params[:id]) + def locked + @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.editable_by(current_user.id) then - respond_to do |format| - if @plan.lock_section(params[:section_id], current_user.id) - format.html { render action: "edit" } - else - format.html { render action: "edit" } - format.json { render json: @plan.errors, status: :unprocessable_entity } - end - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + if !@plan.nil? && user_signed_in? && @plan.readable_by(current_user.id) then + respond_to do |format| + format.json { render json: @plan.locked(params[:section_id],current_user.id) } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end - def unlock_section - @plan = Plan.find(params[:id]) + def delete_recent_locks + @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.editable_by(current_user.id) then - respond_to do |format| - if @plan.unlock_section(params[:section_id], current_user.id) - format.html { render action: "edit" } + if user_signed_in? && @plan.editable_by(current_user.id) then + respond_to do |format| + if @plan.delete_recent_locks(current_user.id) + format.html { render action: "edit" } + else + format.html { render action: "edit" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end - else - format.html { render action: "edit" } - end - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + def unlock_all_sections + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.editable_by(current_user.id) then + respond_to do |format| + if @plan.unlock_all_sections(current_user.id) + format.html { render action: "edit" } + else + format.html { render action: "edit" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end - def answer - @plan = Plan.find(params[:id]) - authorize @plan - if user_signed_in? && @plan.readable_by(current_user.id) then - respond_to do |format| - format.json { render json: @plan.answer(params[:q_id], false).to_json(:include => :options) } - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) - end - end + def lock_section + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.editable_by(current_user.id) then + respond_to do |format| + if @plan.lock_section(params[:section_id], current_user.id) + format.html { render action: "edit" } + else + format.html { render action: "edit" } + format.json { render json: @plan.errors, status: :unprocessable_entity } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end - def export - @plan = Plan.find(params[:id]) + def unlock_section + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.editable_by(current_user.id) then + respond_to do |format| + if @plan.unlock_section(params[:section_id], current_user.id) + format.html { render action: "edit" } + + else + format.html { render action: "edit" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + def answer + @plan = Plan.find(params[:id]) + authorize @plan + if user_signed_in? && @plan.readable_by(current_user.id) then + respond_to do |format| + format.json { render json: @plan.answer(params[:q_id], false).to_json(:include => :options) } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + def export + @plan = Plan.find(params[:id]) authorize @plan - if (user_signed_in? && @plan.readable_by(current_user.id)) then - @exported_plan = ExportedPlan.new.tap do |ep| - ep.plan = @plan - ep.user = current_user ||= nil - #ep.format = request.format.try(:symbol) + if user_signed_in? && @plan.readable_by?(current_user.id) then + @exported_plan = ExportedPlan.new.tap do |ep| + ep.plan = @plan + ep.user = current_user + #ep.format = request.format.try(:symbol) ep.format = request.format.to_sym - plan_settings = @plan.settings(:export) + plan_settings = @plan.settings(:export) - Settings::Dmptemplate::DEFAULT_SETTINGS.each do |key, value| - ep.settings(:export).send("#{key}=", plan_settings.send(key)) - end - end + Settings::Template::DEFAULT_SETTINGS.each do |key, value| + ep.settings(:export).send("#{key}=", plan_settings.send(key)) + end + end - @exported_plan.save! # FIXME: handle invalid request types without erroring? - file_name = @exported_plan.project_name + @exported_plan.save! # FIXME: handle invalid request types without erroring? + file_name = @exported_plan.project_name - respond_to do |format| - format.html - format.xml - format.json - format.csv { send_data @exported_plan.as_csv, filename: "#{file_name}.csv" } - format.text { send_data @exported_plan.as_txt, filename: "#{file_name}.txt" } - format.docx { headers["Content-Disposition"] = "attachment; filename=\"#{file_name}.docx\""} - format.pdf do - @formatting = @plan.settings(:export).formatting - render pdf: file_name, - margin: @formatting[:margin], - footer: { - center: t('helpers.plan.export.pdf.generated_by'), - font_size: 8, - spacing: (@formatting[:margin][:bottom] / 2) - 4, - right: '[page] of [topage]' - } - end - end - - elsif !user_signed_in? then - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - - elsif !@plan.editable_by(current_user.id) then - respond_to do |format| - format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } - end - end - end + respond_to do |format| + format.html + format.xml + format.json + format.csv { send_data @exported_plan.as_csv, filename: "#{file_name}.csv" } + format.text { send_data @exported_plan.as_txt, filename: "#{file_name}.txt" } + format.docx { headers["Content-Disposition"] = "attachment; filename=\"#{file_name}.docx\""} + format.pdf do + @formatting = @plan.settings(:export).formatting + render pdf: file_name, + margin: @formatting[:margin], + footer: { + center: t('helpers.plan.export.pdf.generated_by'), + font_size: 8, + spacing: (@formatting[:margin][:bottom] / 2) - 4, + right: '[page] of [topage]' + } + end + end + elsif !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + elsif !@plan.editable_by(current_user.id) then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end + end + + + + private + + + def get_most_recent( templates ) + groups = Hash.new + templates.each do |t| + k = t.dmptemplate_id + if !groups.has_key?(k) + groups[k] =t + else + other = groups[k] + if other.version < t.version + groups[k] = t + end + end + end + groups.values + end + + + def fixup_hash(plan) + rollup(plan, "notes", "answer_id", "answers") + rollup(plan, "answers", "question_id", "questions") + rollup(plan, "questions", "section_id", "sections") + rollup(plan, "sections", "phase_id", "phases") + + plan["template"]["phases"] = plan.delete("phases") + + ghash = {} + plan["guidance_groups"].map{|g| ghash[g["id"]] = g} + plan["plan_guidance_groups"].each do |pgg| + pgg["guidance_group"] = ghash[ pgg["guidance_group_id"] ] + end + + plan["template"]["org"] = Org.find(plan["template"]["org_id"]).serializable_hash() + end + + + # find all object under src_plan_key + # merge them into the items under obj_plan_key using + # super_id = id + # so we have answers which each have a question_id + # rollup(plan, "answers", "quesiton_id", "questions") + # will put the answers into the right questions. + def rollup(plan, src_plan_key, super_id, obj_plan_key) + id_to_obj = Hash.new() + plan[src_plan_key].each do |o| + id = o[super_id] + if !id_to_obj.has_key?(id) + id_to_obj[id] = Array.new + end + id_to_obj[id] << o + end + + plan[obj_plan_key].each do |o| + id = o["id"] + if id_to_obj.has_key?(id) + o[src_plan_key] = id_to_obj[ id ] + end + end + plan.delete(src_plan_key) + end + end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 0463483..5b98dd2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,199 +1,186 @@ class ProjectsController < ApplicationController - before_filter :get_plan_list_columns, only: %i( index ) + before_filter :get_plan_list_columns, only: %i( index ) after_action :verify_authorized - # GET /projects - # GET /projects.json - def index + # GET /projects + # GET /projects.json + def index authorize Project ## TODO: Is this A magic String? the "Show_shib_link?" as we define it and users dont see cookies - if user_signed_in? then - if (current_user.shibboleth_id.nil? || current_user.shibboleth_id.length == 0) && !cookies[:show_shib_link].nil? && cookies[:show_shib_link] == "show_shib_link" then - flash.notice = "Would you like to #{view_context.link_to I18n.t('helpers.shibboleth_to_link_text'), user_omniauth_shibboleth_path}".html_safe - end + if user_signed_in? then + if (current_user.shibboleth_id.nil? || current_user.shibboleth_id.length == 0) && !cookies[:show_shib_link].nil? && cookies[:show_shib_link] == "show_shib_link" then + flash.notice = "Would you like to #{view_context.link_to I18n.t('helpers.shibboleth_to_link_text'), user_omniauth_shibboleth_path}".html_safe + end - @projects = current_user.projects.filter(params[:filter]) - @has_projects = current_user.projects.any? # unfiltered count + @projects = current_user.projects.filter(params[:filter]) + @has_projects = current_user.projects.any? # unfiltered count - respond_to do |format| - format.html # index.html.erb - end - else - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - end - end - - # GET /projects/1 - # GET /projects/1.json - def show - @project = Project.find(params[:id]) - authorize @project - @show_form = false - if params[:show_form] == "yes" then - @show_form = true - end - if user_signed_in? && @project.readable_by(current_user.id) then - respond_to do |format| - format.html # show.html.erb - end - elsif user_signed_in? then - respond_to do |format| - format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } - end - else - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - end - end - - # GET /projects/new - # GET /projects/new.json - def new - if user_signed_in? then - @project = Project.new - authorize @project - @project.organisation = current_user.organisation - @funders = orgs_of_type(constant("organisation_types.funder"), true) - @templates = get_available_templates - @guidance_groups = get_available_guidance - @always_guidance = get_always_available_guidance - @institutions = orgs_of_type(constant("organisation_types.institution")) - - respond_to do |format| - format.html # new.html.erb - end - else - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - end - end - - # GET /projects/1/edit - # Should this be removed? - def edit - @project = Project.find(params[:id]) - authorize @project - if !user_signed_in? then - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - elsif !@project.editable_by(current_user.id) then - respond_to do |format| - format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } - end - end - end - - def share - @project = Project.find(params[:id]) - authorize @project - if !user_signed_in? then - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - elsif !@project.editable_by(current_user.id) then - respond_to do |format| - format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } - end - end - end - - def export - @project = Project.find(params[:id]) - authorize @project - if !user_signed_in? then - respond_to do |format| - format.html { redirect_to edit_user_registration_path } - end - else - respond_to do |format| - format.html { render action: "export" } - - end - end - end - - # POST /projects - # POST /projects.json - def create - if user_signed_in? then - - attrs = project_params - @project = Project.new(attrs) - authorize @project - - if @project.dmptemplate.nil? && attrs[:funder_id] != "" then # this shouldn't be necessary - see setter for funder_id in project.rb - funder = Organisation.find(attrs[:funder_id]) - if funder.dmptemplates.count == 1 then - @project.dmptemplate = funder.published_templates.first - end - - elsif @project.dmptemplate.nil? || params[:default_tag] == 'true' then - if @project.organisation.nil? || params[:default_tag] == 'true' || @project.organisation.published_templates.first.nil? then - @project.dmptemplate = Dmptemplate.find_by_is_default(true) + respond_to do |format| + format.html # index.html.erb + end else - @project.dmptemplate = @project.organisation.published_templates.first + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end end - end - @project.principal_investigator = current_user.name(false) - - @project.title = I18n.t('helpers.project.my_project_name')+' ('+@project.dmptemplate.title+')' - @project.assign_creator(current_user.id) - respond_to do |format| - if @project.save - format.html { redirect_to({:action => "show", :id => @project.slug, :show_form => "yes"}, {:notice => I18n.t('helpers.project.success')}) } - else - format.html { render action: "new" } - end - end - - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) end - end - # PUT /projects/1 - # PUT /projects/1.json - def update - @project = Project.find(params[:id]) + # GET /projects/1 + # GET /projects/1.json + def show + @project = Project.find(params[:id]) authorize @project - - if user_signed_in? && @project.editable_by(current_user.id) then - attrs = project_params - - if @project.update_attributes(attrs) + @show_form = false + if params[:show_form] == "yes" then + @show_form = true + end + if user_signed_in? && @project.readable_by(current_user.id) then + respond_to do |format| + format.html # show.html.erb + end + elsif user_signed_in? then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + else + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + end + end + + def new + if user_signed_in? then + @plan = Plan.new + authorize @plan + @funders = Org.funder.all + respond_to do |format| - format.html { redirect_to({:action => "show", :id => @project.slug, notice: I18n.t('helpers.project.success_update') }) } + format.html # new.html.erb end else respond_to do |format| - format.html { render action: "edit" } + format.html { redirect_to edit_user_registration_path } end end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) end - end - # DELETE /projects/1 - # DELETE /projects/1.json - def destroy - @project = Project.find(params[:id]) + + # GET /projects/1/edit + # Should this be removed? + def edit + @project = Project.find(params[:id]) authorize @project - if user_signed_in? && @project.editable_by(current_user.id) then - @project.destroy - - respond_to do |format| - format.html { redirect_to projects_url } - end - else - render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + elsif !@project.editable_by(current_user.id) then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end end - end + + def share + @project = Project.find(params[:id]) + authorize @project + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + elsif !@project.editable_by(current_user.id) then + respond_to do |format| + format.html { redirect_to projects_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end + end + + def export + @project = Project.find(params[:id]) + authorize @project + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + else + respond_to do |format| + format.html { render action: "export" } + + end + end + end + + # POST /projects + def create + puts params + return + if user_signed_in? then + @plan = Plan.new(params[:plan]) + authorize @project + if @project.dmptemplate.nil? && params[:project][:funder_id] != "" then # this shouldn't be necessary - see setter for funder_id in project.rb + funder = Org.find(params[:project][:funder_id]) + if funder.dmptemplates.count == 1 then + @project.dmptemplate = funder.published_templates.first + end + elsif @project.dmptemplate.nil? || params[:default_tag] == 'true' then + if @project.organisation.nil? || params[:default_tag] == 'true' || @project.organisation.published_templates.first.nil? then + @project.dmptemplate = Dmptemplate.find_by_is_default(true) + else + @project.dmptemplate = @project.organisation.published_templates.first + end + end + @project.principal_investigator = current_user.name(false) + + @project.title = I18n.t('helpers.project.my_project_name')+' ('+@project.dmptemplate.title+')' + @project.assign_creator(current_user.id) + respond_to do |format| + if @project.save + format.html { redirect_to({:action => "show", :id => @project.slug, :show_form => "yes"}, {:notice => I18n.t('helpers.project.success')}) } + else + format.html { render action: "new" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + # PUT /projects/1 + # PUT /projects/1.json + def update + @project = Project.find(params[:id]) + authorize @project + if user_signed_in? && @project.editable_by(current_user.id) then + if @project.update_attributes(params[:project]) + respond_to do |format| + format.html { redirect_to({:action => "show", :id => @project.slug, notice: I18n.t('helpers.project.success_update') }) } + end + else + respond_to do |format| + format.html { render action: "edit" } + end + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end + + # DELETE /projects/1 + # DELETE /projects/1.json + def destroy + @project = Project.find(params[:id]) + authorize @project + if user_signed_in? && @project.editable_by(current_user.id) then + @project.destroy + + respond_to do |format| + format.html { redirect_to projects_url } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end # returns to AJAX call from frontend # difficult to secure as it passes through params, and dosent curate data based @@ -249,7 +236,7 @@ else institution = nil end - excluded_orgs = orgs_of_type(constant("organisation_types.funder")) + orgs_of_type(constant("organisation_types.institution")) + Org.orgs_with_parent_of_type(constant("organisation_types.institution")) + excluded_orgs = Org.funders + Org.institutions guidance_groups = {} ggs = GuidanceGroup.guidance_groups_excluding(excluded_orgs) @@ -322,7 +309,7 @@ # ----------------------------------------------------------- def get_available_templates - Dmptemplate.where(published: true) + Template.find_by(published: true) end # ----------------------------------------------------------- diff --git a/app/controllers/settings/plans_controller.rb b/app/controllers/settings/plans_controller.rb index 2c65aea..9708521 100644 --- a/app/controllers/settings/plans_controller.rb +++ b/app/controllers/settings/plans_controller.rb @@ -1,14 +1,17 @@ module Settings class PlansController < SettingsController + before_filter :get_plan_list_columns before_filter :get_settings + after_action :verify_authorized def show - authorize @plan + authorize [:settings, @plan] respond_to do |format| format.html format.partial + format.json{ render json: settings_json } end end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index da81ea8..cded887 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -15,24 +15,22 @@ def roadmap end - # GET /projects/publicly_available + # GET /plans/publicly_available # ----------------------------------------------------------- def public_plans - @projects = Project.publicly_visible.order(title: :asc) + @plans = Plan.where(visibility: :publicly_visible).order(title: :asc) end - # GET /projects/[:project_slug]/public_export + # GET /plans/[:plan_slug]/public_export # ------------------------------------------------------------- def public_export - @project = Project.find(params[:id]) + @plan = Plan.find(params[:id]) # Force PDF response request.format = :pdf # if the project is designated as public - if @project.visibility == :publicly_visible - @plan = @project.plans.first - + if @plan.visibility == :publicly_visible if !@plan.nil? @exported_plan = ExportedPlan.new.tap do |ep| ep.plan = @plan diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index 385493e..50f3c60 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -9,23 +9,141 @@ # GET /dmptemplates def admin_index authorize Template - #institutional templates - all_versions_own_templates = Template.where(org_id: current_user.org_id, customization_of: nil).order(:version) + # institutional templates + all_versions_own_templates = Template.where(org_id: current_user.org_id, customization_of: nil).order(version: :desc) current_templates = {} + # take most recent version of each template all_versions_own_templates.each do |temp| if current_templates[temp.dmptemplate_id].nil? current_templates[temp.dmptemplate_id] = temp end end @templates_own = current_templates.values - #funders templates - @templates_funders = Template.funders_templates + @other_published_version = {} + current_templates.keys.each do |dmptemplate_id| + @other_published_version[dmptemplate_id] = Template.where(org_id: current_user.org_id, dmptemplate_id: dmptemplate_id, published: true).present? + end + + # funders templates + funders_templates = {} + Org.includes(:templates).funder.each do |org| + org.templates.where(customization_of: nil, published: true).order(version: :desc).each do |temp| + if funders_templates[temp.dmptemplate_id].nil? + funders_templates[temp.dmptemplate_id] = temp + end + end + end + + @templates_funders = funders_templates.values + # are any funder templates customized + @templates_customizations = {} + Template.where(org_id: current_user.org_id, customization_of: funders_templates.keys).order(version: :desc).each do |temp| + if @templates_customizations[temp.customization_of].nil? + @templates_customizations[temp.customization_of] = {} + @templates_customizations[temp.customization_of][:temp] = temp + @templates_customizations[temp.customization_of][:published] = temp.published + else + @templates_customizations[temp.customization_of][:published] = @templates_customizations[temp.customization_of][:published] || temp.published + end + end end # GET /dmptemplates/1 def admin_template @template = Template.find(params[:id]) + # check to see if this is a funder template needing customized + if @template.org_id != current_user.org_id + # definitely need to deep_copy the given 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 + # need to mark all Phases, questions, sections as not-modifiable + 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 + end + end + customizations = Template.includes(phases: [sections: [questions: :suggested_answers ]]).where(org_id: current_user.org_id, customization_of: @template.dmptemplate_id).order(version: :desc) + if customizations.present? + # existing customization 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 customized 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: :suggested_answers]).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 suggested_answers + question.suggested_answers.each do |suggested_answer| + suggested_answer_copy = SuggestedAnswer.deep_copy(suggested_answer) + suggested_answer_copy.org_id = current_user.org_id + suggested_answer_copy.question_id = customization_question.id + suggested_answer_copy.save! + end + # guidance attached to a question is also a form of customization + # It will soon become an annotation of the question, and be combined with + # suggested answers + customization_question.guidance = customization_question.guidance + question.guidance + customization_question.save! + end + end + end + end + end + else + # first time customization + new_customization.version = 0 + new_customization.dmptemplate_id = loop do + random = rand 2147483647 # max int field in psql + break random unless Template.exists?(dmptemplate_id: random) + end + end + new_customization.save! + @template = new_customization + end + # needed for some post-migration edge cases + # some customized templates which were edited + if @template.published + new_version = Template.deep_copy(@template) + new_version.version = @template.version + 1 + new_version.published = false + new_version.save! + @template = new_version + end authorize @template end @@ -34,9 +152,20 @@ def admin_update @template = Template.find(params[:id]) authorize @template + if @template.published? + # published templates cannot be edited + redirect_to admin_template_template_path(@template), notice: I18n.t('org_admin.templates.read_only') and return + end @template.description = params["template-desc"] if @template.update_attributes(params[:template]) - redirect_to admin_template_template_path(params[:template]), notice: I18n.t('org_admin.templates.updated_message') + if @template.published + # create a new template version if this template became published + new_version = Template.deep_copy(@template) + new_version.version = @template.version + 1 + new_version.published = false + new_version.save! + end + redirect_to admin_index_template_path(), notice: I18n.t('org_admin.templates.updated_message') else render action: "edit" end @@ -50,10 +179,18 @@ # POST /dmptemplates + # creates a new template with version 0 and new dmptemplate_id def admin_create @template = Template.new(params[:template]) @template.org_id = current_user.org_id @template.description = params['template-desc'] + @template.published = false + @template.version = 0 + # Generate a unique identifier for the dmptemplate_id + @template.dmptemplate_id = loop do + random = rand 2147483647 + break random unless Template.exists?(dmptemplate_id: random) + end authorize @template if @template.save redirect_to admin_template_template_path(@template), notice: I18n.t('org_admin.templates.created_message') @@ -71,6 +208,13 @@ redirect_to admin_index_template_path end + # GET /templates/1 + def admin_template_history + @template = Template.find(params[:id]) + authorize @template + @templates = Template.where(dmptemplate_id: @template.dmptemplate_id).order(:version) + end + # PHASES @@ -82,7 +226,7 @@ @edit = params[:edit] == "true" ? true : false #verify if there are any sections if not create one @sections = @phase.sections - if !@sections.any?() || @sections.count == 0 then + if !@sections.any?() || @sections.count == 0 @section = @phase.sections.build @section.phase = @phase @section.title = '' @@ -93,11 +237,11 @@ @new_sec = true end #verify if section_id has been passed, if so then open that section - if params.has_key?(:section_id) then + if params.has_key?(:section_id) @open = true @section_id = params[:section_id].to_i end - if params.has_key?(:question_id) then + if params.has_key?(:question_id) @question_id = params[:question_id].to_i end end @@ -105,8 +249,9 @@ #preview a phase def admin_previewphase - @template = Template.find(params[:id]) - authorize @template + @phase = Phase.find(params[:id]) + authorize @phase.template + @template = @phase.template end @@ -160,6 +305,7 @@ @section = Section.new(params[:section]) authorize @section.phase.template @section.description = params["section-desc"] + @section.modifiable = true if @section.save redirect_to admin_phase_template_path(id: @section.phase_id, :section_id => @section.id, edit: 'true'), notice: I18n.t('org_admin.templates.created_message') @@ -201,7 +347,7 @@ authorize @question.section.phase.template @question.guidance = params["new-question-guidance"] @question.default_value = params["new-question-default-value"] - if @question.save + if @question.save! redirect_to admin_phase_template_path(id: @question.section.phase_id, section_id: @question.section_id, question_id: @question.id, edit: 'true'), notice: I18n.t('org_admin.templates.created_message') else render action: "admin_phase" diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 9b65b77..593e146 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -7,7 +7,7 @@ handle_omniauth(scheme) end end - + ## # Processes callbacks from an omniauth provider and directs the user to # the appropriate page: diff --git a/app/controllers/users/omniauth_shibboleth_request_controller.rb b/app/controllers/users/omniauth_shibboleth_request_controller.rb index 43e363d..3190c2f 100644 --- a/app/controllers/users/omniauth_shibboleth_request_controller.rb +++ b/app/controllers/users/omniauth_shibboleth_request_controller.rb @@ -2,8 +2,8 @@ before_filter :authenticate_user!, only: :associate def redirect - if !current_user.nil? && !current_user.organisation.nil? - idp = params[:idp] || current_user.organisation.wayfless_entity + if !current_user.nil? && !current_user.org.nil? + idp = params[:idp] || current_user.org.wayfless_entity else idp = params[:idp] end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 73851a3..0d8de1a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -23,4 +23,10 @@ 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 + def isActivePage(path) + return request.fullpath() == path + end end diff --git a/app/helpers/plans_helper.rb b/app/helpers/plans_helper.rb new file mode 100644 index 0000000..8cdd5d9 --- /dev/null +++ b/app/helpers/plans_helper.rb @@ -0,0 +1,84 @@ +module PlansHelper + + # Build variable column headings for the project list + # -------------------------------------------------------- + def plan_list_column_heading(column) + if column.kind_of?(Array) + heading = (column.first.kind_of?(String) ? column.first : t("helpers.project.columns.unknown")) + + elsif column.kind_of?(String) + heading = column + + else + heading = t("helpers.project.columns.unknown") + end + + klass = (['name', 'description'].include?(heading) ? :dmp_th_big : :dmp_th_small) + + content_tag(:th, t("helpers.project.columns.#{heading}"), class: klass) + end + + # Populate a variable column for the project list + # -------------------------------------------------------- + def plan_list_column_body(column, plan) + + col = (column.kind_of?(Array) ? column[0] : column) + + klass, content = case col + when 'name' + [ "dmp_td_big", link_to(plan.title, plan_path(I18n.locale, plan), class: "dmp_table_link") ] + + when 'owner' + user = plan.owner + + text = if user.nil? + t("helpers.project.columns.unknown") + elsif user == current_user + t("helpers.me") + else + user.name + end + + [ "tmp_td_small", text ] + when 'shared' + shared_num = plan.users.count - 1 + text = shared_num > 0 ? (t("helpers.yes_label") + " (with #{shared_num} people) ") : t("helpers.no_label") + [ "dmp_td_small", text ] + when 'visibility' + ["dmp_td_small", (plan.visibility.nil? ? I18n.t("helpers.project.visibilities.labels.organisationally_visible") : I18n.t("helpers.project.visibilities.labels.#{plan.visibility}"))] + when 'last_edited' + [ "dmp_td_small", l(plan.latest_update.to_date, formats: :short) ] + when 'description' + [ "dmp_td_medium", (plan.try(col) || t("helpers.settings.unknown")) ] + when 'non_link_name' + [ "dmp_td_big", plan.title ] + when 'template' + ["dmp_td_big", plan.template.title] + when 'organisation' + ["dmp_td_medium", (plan.owner.org.nil? ? t("helpers.settings.unknown") : plan.owner.org.name) ] + else + [ "dmp_td_small", (plan.try(col) || t("helpers.settings.unknown")) ] + end + + content_tag(:td, content, class: klass) + end + + # 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 + +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb deleted file mode 100644 index 84c458d..0000000 --- a/app/helpers/projects_helper.rb +++ /dev/null @@ -1,84 +0,0 @@ -module ProjectsHelper - - # Build variable column headings for the project list - # -------------------------------------------------------- - def project_list_column_heading(column) - if column.kind_of?(Array) - heading = (column.first.kind_of?(String) ? column.first : t("helpers.project.columns.unknown")) - - elsif column.kind_of?(String) - heading = column - - else - heading = t("helpers.project.columns.unknown") - end - - klass = (['name', 'description'].include?(heading) ? :dmp_th_big : :dmp_th_small) - - content_tag(:th, t("helpers.project.columns.#{heading}"), class: klass) - end - - # Populate a variable column for the project list - # -------------------------------------------------------- - def project_list_column_body(column, project) - - col = (column.kind_of?(Array) ? column[0] : column) - - klass, content = case col - when 'name' - [ "dmp_td_big", link_to(project.title, project_path(project), class: "dmp_table_link") ] - - when 'owner' - user = project.owner - - text = if user.nil? - "Unknown" - elsif user == current_user - t("helpers.me") - else - user.name - end - - [ "tmp_td_small", text ] - when 'shared' - shared_num = project.project_groups.count - 1 - text = shared_num > 0 ? (t("helpers.yes_label") + " (with #{shared_num} people) ") : t("helpers.no_label") - [ "dmp_td_small", text ] - when 'visibility' - ["dmp_td_small", (project.visibility.nil? ? I18n.t("helpers.project.visibilities.labels.organisationally_visible") : I18n.t("helpers.project.visibilities.labels.#{project.visibility}"))] - when 'last_edited' - [ "dmp_td_small", l(project.latest_update.to_date, formats: :short) ] - when 'description' - [ "dmp_td_medium", (project.try(col) || "Unknown") ] - when 'non_link_name' - [ "dmp_td_big", project.title ] - when 'template' - ["dmp_td_big", project.dmptemplate.title] - when 'organisation' - ["dmp_td_medium", (project.organisation.nil? ? project.owner.organisation.name : project.organisation.name)] - else - [ "dmp_td_small", (project.try(col) || "Unknown") ] - end - - content_tag(:td, content, class: klass) - end - - # 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.project.dmptemplate.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 - -end diff --git a/app/models/ability.rb b/app/models/ability.rb deleted file mode 100644 index ba5e6fa..0000000 --- a/app/models/ability.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Ability - include GlobalHelpers - include CanCan::Ability - - def initialize(user) - # Define abilities for the passed in user here. For example: - # - user ||= User.new # guest user (not logged in) - if user.has_role? constant("roles.super_admin") - can :manage, :all - else - can :read, :all - end - - can :manage_settings, User do |viewed_user| - viewed_user.present? && user.id == viewed_user.id - end - # - # The first argument to `can` is the action you are giving the user - # permission to do. - # If you pass :manage it will apply to every action. Other common actions - # here are :read, :create, :update and :destroy. - # - # The second argument is the resource the user can perform the action on. - # If you pass :all it will apply to every resource. Otherwise pass a Ruby - # class of the resource. - # - # The third argument is an optional hash of conditions to further filter the - # objects. - # For example, here the user can only update published articles. - # - # can :update, Article, :published => true - # - # See the wiki for details: - # https://github.com/ryanb/cancan/wiki/Defining-Abilities - - end -end diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb deleted file mode 100644 index 511e649..0000000 --- a/app/models/admin_user.rb +++ /dev/null @@ -1,4 +0,0 @@ -class AdminUser < ActiveRecord::Base - devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable - attr_accessible :email, :password, :password_confirmation, :remember_me -end diff --git a/app/models/answer.rb b/app/models/answer.rb index 00705da..35e0c2d 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -5,19 +5,35 @@ belongs_to :question belongs_to :user belongs_to :plan + has_many :notes, dependent: :destroy has_and_belongs_to_many :question_options, join_table: "answers_question_options" + has_many :notes + ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :text, :plan_id, :question_id, :user_id, :option_ids, - :question, :user, :plan, :as => [:default, :admin] + attr_accessible :text, :plan_id, :question_id, :user_id, :question_option_ids, + :question, :user, :plan, :question_options, :notes, :note_ids, + :as => [:default, :admin] ## # Validations - validates :user, :plan, :question, :text, presence: true - - # Make sure there is only one answer per question! - validates :question, uniqueness: {scope: [:user, :plan], - message: I18n.t('helpers.errors.answer.only_one_per_question')} +# validates :user, :plan, :question, presence: true +# +# # Make sure there is only one answer per question! +# validates :question, uniqueness: {scope: [:plan], +# message: I18n.t('helpers.answer.only_one_per_question')} +# +# # The answer MUST have a text value if the question is NOT option based or a question_option if +# # it is option based. +# validates :text, presence: true, if: Proc.new{|a| +# (a.question.nil? ? false : !a.question.question_format.option_based?) +# } +# validates :question_options, presence: true, if: Proc.new{|a| +# (a.question.nil? ? false : a.question.question_format.option_based?) +# } +# +# # Make sure the plan and question are associated with the same template! +# validates :plan, :question, answer_for_correct_template: true end diff --git a/app/models/exported_plan.rb b/app/models/exported_plan.rb index e1b18fb..4045298 100644 --- a/app/models/exported_plan.rb +++ b/app/models/exported_plan.rb @@ -16,62 +16,64 @@ # Store settings with the exported plan so it can be recreated later # if necessary (otherwise the settings associated with the plan at a # given time can be lost) - has_settings :export, class_name: 'Settings::Dmptemplate' do |s| - s.key :export, defaults: Settings::Dmptemplate::DEFAULT_SETTINGS + has_settings :export, class_name: 'Settings::Template' do |s| + s.key :export, defaults: Settings::Template::DEFAULT_SETTINGS end # TODO: Consider removing the accessor methods, they add no value. The view/controller could # just access the value directly from the project/plan: exported_plan.plan.project.title - # Getters to match Settings::Dmptemplate::VALID_ADMIN_FIELDS + # Getters to match Settings::Template::VALID_ADMIN_FIELDS def project_name - name = self.plan.project.title - name += " - #{self.plan.title}" if self.plan.project.dmptemplate.phases.count > 1 + name = self.plan.template.title + name += " - #{self.plan.title}" if self.plan.template.phases.count > 1 name end def project_identifier - self.plan.project.identifier + self.plan.identifier end def grant_title - self.plan.project.grant_number + self.plan.grant_number end def principal_investigator - self.plan.project.principal_investigator + self.plan.principal_investigator end def project_data_contact - self.plan.project.data_contact + self.plan.data_contact end def project_description - self.plan.project.description + self.plan.description end def funder - org = self.plan.project.dmptemplate.try(:organisation) - org.name if org.present? && org.organisation_type.try(:name) == constant("organisation_types.funder") + org = self.plan.template.try(:org) + org.name if org.present? && org.funder? end def institution - plan.project.organisation.try(:name) + plan.owner.org.try(:name) end def orcid scheme = IdentifierScheme.find_by(name: 'orcid') - if self.user.nil? + if self.owner.nil? '' else - orcid = self.user.user_identifiers.where(identifier_scheme: scheme).first + orcid = self.owner.user_identifiers.where(identifier_scheme: scheme).first (orcid.nil? ? '' : orcid.identifier) end end +# TODO: This looks like it will always return an empty array as questions is undefined # sections taken from fields settings def sections - sections = self.plan.sections + # TODO: How do we know which phase to use here!? + sections = self.template.phases.first.sections return [] if questions.empty? diff --git a/app/models/file_upload.rb b/app/models/file_upload.rb deleted file mode 100644 index eef1ca3..0000000 --- a/app/models/file_upload.rb +++ /dev/null @@ -1,5 +0,0 @@ -class FileUpload < ActiveRecord::Base - ## - # Associations - belongs_to :file_type -end diff --git a/app/models/guidance.rb b/app/models/guidance.rb index c2f93fb..3b15e2a 100644 --- a/app/models/guidance.rb +++ b/app/models/guidance.rb @@ -13,8 +13,11 @@ ## # Associations belongs_to :guidance_group - belongs_to :question +# belongs_to :question has_and_belongs_to_many :themes, join_table: "themes_in_guidance" +# depricated, but required for migration "single_group_for_guidance" + has_and_belongs_to_many :guidance_groups, join_table: "guidance_in_group" + @@ -34,9 +37,11 @@ # @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) - if guidance_group.org_id == org_id - return true - end + unless guidance_group.nil? + if guidance_group.org.id == org_id + return true + end + end return false end @@ -45,7 +50,7 @@ # # @param org_id [Integer] the integer id for an organisation # @return [Array] list of guidance - def self.by_organisation(org_id) + def self.by_org(org_id) org_guidance = [] # TODO: re-write below querry when guidance_in_group removed from model Org.find_by(id: org_id).guidance_groups.each do |group| @@ -84,27 +89,24 @@ guidance = Guidance.find_by(id: id) viewable = false - # guidances may belong to many guidance groups, so we check the above case for each - guidance.guidance_groups.each do |guidance_group| - - # guidances are viewable if they are owned by any of the user's organisations - user.org do |org| - if guidance_group.org.id == org.id + unless guidance.nil? + unless guidance.guidance_group.nil? + # guidances are viewable if they are owned by any of the user's organisations + if guidance.guidance_group.org == user.org + viewable = true + end + # 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 + + # guidance groups are viewable if they are owned by a funder + if Org.funders.include?(guidance.guidance_group.org) viewable = true end end - - # guidance groups are viewable if they are owned by the Managing Curation Center - if guidance_group.organisation.id == Org.find_by( name: GlobalHelpers.constant("organisation_types.managing_organisation")).id - viewable = true - end - - # guidance groups are viewable if they are owned by a funder - if guidance_group.organisation.organisation_type == OrganisationType.find_by( name: GlobalHelpers.constant("organisation_types.funder")) - viewable = true - end end - + return viewable end @@ -118,20 +120,20 @@ # @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.find_by name: GlobalHelpers.constant("organisation_types.managing_organisation")).guidance_groups + managing_groups = Org.managing_orgs.collect{|o| o.guidance_groups} # find all groups owned by a Funder organisation funder_groups = [] - funders = OrganisationType.find_by( name: GlobalHelpers.constant("organisation_types.funder")) - funders.organisations.each do |funder| + funders = Org.funders + funders.each do |funder| funder_groups += funder.guidance_groups end # find all groups owned by any of the user's organisations - organisation_groups = user.organisation.guidance_groups + organisation_groups = user.org.guidance_groups # find all guidances belonging to any of the viewable groups all_viewable_guidances = [] all_viewable_groups = managing_groups + funder_groups + organisation_groups - all_viewable_groups.each do |group| + all_viewable_groups.flatten.each do |group| all_viewable_guidances += group.guidances end # pass the list of viewable guidances to the view diff --git a/app/models/guidance_group.rb b/app/models/guidance_group.rb index cc4bd8f..71535c3 100644 --- a/app/models/guidance_group.rb +++ b/app/models/guidance_group.rb @@ -10,11 +10,10 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :organisation_id, :name, :optional_subset, :published, - :org, :as => [:default, :admin] - attr_accessible :dmptemplate_ids, :as => [:default, :admin] + attr_accessible :org_id, :name, :optional_subset, :published, :org, :guidances, + :as => [:default, :admin] - + validates :name, :org, presence: true # EVALUATE CLASS AND INSTANCE METHODS BELOW @@ -24,16 +23,7 @@ - - validates :name, :org, presence: true - - ## - # Converts a guidance group to a string containing the display name - # - # @return [String] the name of the organisation, with or without the name of the guidance group - def to_s - "#{display_name}" - end + ## # Converts the current guidance group to a string containing the display name. @@ -43,10 +33,10 @@ # # @return [String] the display name for the guidance group def display_name - if organisation.guidance_groups.count > 1 - return "#{organisation.name}: #{name}" + if org.guidance_groups.count > 1 + return "#{org.name}: #{name}" else - return organisation.name + return org.name end end @@ -55,14 +45,20 @@ # # @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 - excluded_orgs.each do |org| - excluded_org_ids << org.id - end - return_orgs = GuidanceGroup.where("organisation_id NOT IN (?)", excluded_org_ids) - return return_orgs - end + 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 ## # Returns whether or not a given user can view a given guidance group @@ -79,17 +75,17 @@ viewable = false # groups are viewable if they are owned by any of the user's organisations - if guidance_group.organisation == user.organisation + if guidance_group.org == user.org viewable = true end # groups are viewable if they are owned by the managing curation center Org.where( name: GlobalHelpers.constant("organisation_types.managing_organisation")).find_each do |managing_group| - if guidance_group.organisation.id == managing_group.id + if guidance_group.org.id == managing_group.id viewable = true end end # groups are viewable if they are owned by a funder - if guidance_group.organisation.organisation_type == OrganisationType.find_by( name: GlobalHelpers.constant("organisation_types.funder")) + if guidance_group.org.org_type == 2 viewable = true end @@ -108,17 +104,17 @@ def self.all_viewable(user) # first find all groups owned by the Managing Curation Center managing_org_groups = [] - Org.where( name: GlobalHelpers.constant("organisation_types.managing_organisation")).find_each do |managing_org| + Org.where(name: GlobalHelpers.constant("organisation_types.managing_organisation")).find_each do |managing_org| managing_org_groups = managing_org_groups + managing_org.guidance_groups end # find all groups owned by a Funder organisation funder_groups = [] - funders = OrganisationType.find_by( name: GlobalHelpers.constant("organisation_types.funder")) - funders.organisations.each do |funder| + funders = Org.where(org_type: 2) + funders.each do |funder| funder_groups = funder_groups + funder.guidance_groups end - organisation_groups = [user.organisation.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 diff --git a/app/models/new_plan.rb b/app/models/new_plan.rb deleted file mode 100644 index 1dac7cd..0000000 --- a/app/models/new_plan.rb +++ /dev/null @@ -1,5 +0,0 @@ -class NewPlan < ActiveRecord::Base - belongs_to :template - has_many :roles - has_many :users, through: :roles -end diff --git a/app/models/note.rb b/app/models/note.rb index fc0b3ba..74b3078 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -7,6 +7,8 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :question_id, :text, :user_id, :archived, :plan_id, :archived_by, + attr_accessible :text, :user_id, :answer_id, :archived, :archived_by, :answer, :user, :as => [:default, :admin] + + validates :text, :answer, :user, presence: true end diff --git a/app/models/org.rb b/app/models/org.rb index bd32c00..15dcd92 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -19,9 +19,9 @@ # -relies on protected_attributes gem as syntax depricated in rails 4.2 attr_accessible :abbreviation, :banner_text, :logo, :remove_logo, :logo_file_name, :name, :target_url, - :organisation_type_id, :wayfless_entity, :parent_id, :sort_name, - :token_permission_type_ids, :language_id, :contact_email, :language, - :org_type, :token_permission_types + :organisation_type_id, :wayfless_entity, :parent_id, :sort_name, + :token_permission_type_ids, :language_id, :contact_email, + :language, :org_type, :region, :token_permission_types ## # Validators @@ -46,6 +46,10 @@ 6 => :school, column: 'org_type' + # Predefined queries for retrieving the managain organisation and funders + scope :managing_orgs, -> { where(name: GlobalHelpers.constant("organisation_types.managing_organisation")) } + scope :funders, -> { where(org_type: 2) } + scope :institutions, -> { where(org_type: 3) } # EVALUATE CLASS AND INSTANCE METHODS BELOW @@ -53,7 +57,11 @@ # What do they do? do they do it efficiently, and do we need them? - +# TODO: Should these be hardcoded? Also, an Org can currently be multiple org_types at one time. +# For example you can do: funder = true; project = true; school = true +# Calling type in the above scenario returns "Funder" which is a bit misleading +# Is FlagShihTzu's Bit flag the appropriate structure here or should we use an enum? +# Tests are setup currently to work with this issue. ## # returns the name of the type of the organisation as a string # defaults to none if no org type present @@ -66,7 +74,7 @@ return "Funder" elsif self.organisation? return "Organisation" - elsif @org.research_institute? + elsif self.research_institute? return "Research Institute" elsif self.project? return "Project" @@ -102,6 +110,7 @@ # # @param [String] the name of an organisation type # @return [Array] +=begin def self.orgs_with_parent_of_type(org_type) parents = OrganisationType.find_by_name(org_type).organisations children = Array.new @@ -110,7 +119,7 @@ end return children end - + ## # returns a list of all guidance groups belonging to other organisations # @@ -145,7 +154,7 @@ end return organisations_list end - + ## # returns a list of all sections of a given version from this organisation and it's parents # @@ -162,7 +171,7 @@ return sections.where("version_id = ? ", version_id).all + parent.all_sections(version_id) end end - + ## # returns the guidance groups of this organisation and all of it's children # @@ -174,7 +183,7 @@ end return ggs end - + ## # returns the highest parent organisation in the tree # @@ -186,7 +195,8 @@ return parent.root end end - +=end + ## # returns all published templates belonging to the organisation # diff --git a/app/models/org_token_permission.rb b/app/models/org_token_permission.rb deleted file mode 100644 index 894f84f..0000000 --- a/app/models/org_token_permission.rb +++ /dev/null @@ -1,11 +0,0 @@ -class OrgTokenPermission < ActiveRecord::Base - ## - # Associations - belongs_to :organisation - belongs_to :token_permission_type - - ## - # Possibly needed for active_admin - # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :organisation_id, :token_permission_type_id, :organisation, :token_permission_type, :as => [:default, :admin] -end diff --git a/app/models/organisation_type.rb b/app/models/organisation_type.rb deleted file mode 100644 index d69aa48..0000000 --- a/app/models/organisation_type.rb +++ /dev/null @@ -1,14 +0,0 @@ -class OrganisationType < ActiveRecord::Base - ## - # Attributes - has_many :orgs - - ## - # Possibly needed for active_admin - # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :description, :organisations, :name, :as => [:default, :admin] - - ## - # Validators - validates :name, presence: true, uniqueness: true -end diff --git a/app/models/perm.rb b/app/models/perm.rb index 7d17efb..114c0f0 100644 --- a/app/models/perm.rb +++ b/app/models/perm.rb @@ -7,4 +7,6 @@ # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 attr_accessible :name, :as => [:default, :admin] + + validates :name, presence: true, uniqueness: true end diff --git a/app/models/phase.rb b/app/models/phase.rb index fa5109c..88429fd 100644 --- a/app/models/phase.rb +++ b/app/models/phase.rb @@ -4,7 +4,7 @@ # [+Created:+] 03/09/2014 # [+Copyright:+] Digital Curation Centre and University of California Curation Center class Phase < ActiveRecord::Base - extend FriendlyId +# extend FriendlyId ## # Associations @@ -20,9 +20,10 @@ ## # sluggable title - friendly_id :title, use: [:slugged, :history, :finders] + #friendly_id :title, use: [:slugged, :history, :finders] + validates :title, :number, :template, presence: true @@ -45,26 +46,28 @@ "#{title}" end +# TODO: This function does not belong here anymore. It may be useless now. ## # returns either the latest published version of this phase # also serves to verify if this phase has any published versions as returns nil # if there are no published versions # # @return [Version, nil] - def latest_published_version - pub_vers = versions.where('published = ?', true).order('updated_at DESC') - if pub_vers.any?() then - return pub_vers.first - else - return nil - end - end +# def latest_published_version +# pub_vers = versions.where('published = ?', true).order('updated_at DESC') +# if pub_vers.any?() then +# return pub_vers.first +# else +# return nil +# end +# end # TODO: reevaluate this method. It seems like the 1st query is unecessary ## # verify if a phase has a published version or a version with one or more sections # # @return [Boolean] +=begin def has_sections versions = self.versions.where('published = ?', true).order('updated_at DESC') if versions.any? then @@ -84,4 +87,21 @@ end return has_section end +=end + + ## + # deep copy the given phase and all it's associations + # + # @params [Phase] phase to be deep copied + # @return [Phase] the saved, copied phase + def self.deep_copy(phase) + phase_copy = phase.dup + phase_copy.save! + phase.sections.each do |section| + section_copy = Section.deep_copy(section) + section_copy.phase_id = phase_copy.id + section_copy.save! + end + return phase_copy + end end diff --git a/app/models/plan.rb b/app/models/plan.rb index a6c36c6..a32b171 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -5,90 +5,79 @@ has_many :phases, through: :template has_many :sections, through: :phases has_many :questions, through: :sections - has_many :answers + has_many :themes, through: :questions + has_many :answers, dependent: :destroy has_many :notes, through: :answers + has_many :roles, dependent: :destroy has_many :users, through: :roles + has_many :plan_guidance_groups, dependent: :destroy + has_many :guidance_groups, through: :plan_guidance_groups + + accepts_nested_attributes_for :template + has_many :exported_plans + + has_many :roles +# COMMENTED OUT THE DIRECT CONNECTION HERE TO Users to prevent assignment of users without an access_level specified (currently defaults to creator) +# has_many :users, through: :roles + ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :locked, :project_id, :version_id, :version, :plan_sections, - :exported_plans, :project, :as => [:default, :admin] + attr_accessible :locked, :project_id, :version_id, :version, :plan_sections, + :exported_plans, :project, :title, :template, :grant_number, + :identifier, :principal_investigator, :principal_investigator_identifier, + :description, :data_contact, :funder_name, :visibility, :exported_plans, + :roles, :users, :org, :as => [:default, :admin] + accepts_nested_attributes_for :roles # public is a Ruby keyword so using publicly enum visibility: [:organisationally_visible, :publicly_visible, :is_test, :privately_visible] + #TODO: work out why this messes up plan creation : + # briley: Removed reliance on :users, its really on :roles (shouldn't have a plan without at least a creator right?) It should be ok like this though now +# validates :template, :title, presence: true + ## # Constants - A4_PAGE_HEIGHT = 297 #(in mm) - A4_PAGE_WIDTH = 210 #(in mm) - ROUNDING = 5 #round estimate up to nearest 5% - FONT_HEIGHT_CONVERSION_FACTOR = 0.35278 #convert font point size to mm - FONT_WIDTH_HEIGHT_RATIO = 0.4 #Assume glyph width averages 2/5 the height + A4_PAGE_HEIGHT = 297 #(in mm) + A4_PAGE_WIDTH = 210 #(in mm) + ROUNDING = 5 #round estimate up to nearest 5% + FONT_HEIGHT_CONVERSION_FACTOR = 0.35278 #convert font point size to mm + FONT_WIDTH_HEIGHT_RATIO = 0.4 #Assume glyph width averages 2/5 the height ## # Settings for the template - has_settings :export, class_name: 'Settings::Template' do |s| - s.key :export, defaults: Settings::Template::DEFAULT_SETTINGS - end - alias_method :super_settings, :settings - - - - - - - - # EVALUATE CLASS AND INSTANCE METHODS BELOW - # - # What do they do? do they do it efficiently, and do we need them? - # Special note, these are both the methods in the old plan, and in the old project - - - - - - - + has_settings :export, class_name: 'Settings::Template' do |s| + s.key :export, defaults: Settings::Template::DEFAULT_SETTINGS + end + alias_method :super_settings, :settings ## - # Proxy through to the template settings (or defaults if this plan doesn't have - # an associated template) if there are no settings stored for this plan. - # `key` is required by rails-settings, so it's required here, too. + # Proxy through to the template settings (or defaults if this plan doesn't have + # an associated template) if there are no settings stored for this plan. + # `key` is required by rails-settings, so it's required here, too. # # @param key [Key] a key required by rails # @return [Settings] settings for this plan's template - def settings(key) - self_settings = self.super_settings(key) - return self_settings if self_settings.value? - self.dmptemplate.settings(key) - end + def settings(key) + self_settings = self.super_settings(key) + return self_settings if self_settings.value? +# self.dmptemplate.settings(key) + self.template.settings(key) unless self.template.nil? + end ## # returns the template for this plan, or generates an empty template and returns that # # @return [Dmptemplate] the template associated with this plan def dmptemplate - self.project.try(:dmptemplate) || Dmptemplate.new + #self.project.try(:dmptemplate) || Dmptemplate.new + self.template end - ## - # returns the title for this project as defined by the settings - # - # @return [String] the title for this project - def title - logger.debug "Title in settings: #{self.settings(:export).title}" - if self.settings(:export).title == "" - if !self.version.nil? && !self.version.phase.nil? && !self.version.phase.title? then - return self.version.phase.title - else - return I18n.t('tool_title2') - end - else - return self.settings(:export).title - end - end + ## # returns the most recent answer to the given question id @@ -106,69 +95,91 @@ answer.question_id = qid answer.text = question.default_value default_options = Array.new - question.options.each do |option| + question.question_options.each do |option| if option.is_default default_options << option end end - answer.options = default_options + answer.question_options = default_options end return answer end +# TODO: This just retrieves all of the guidance associated with the themes within the template +# so why are we transferring it here to the plan? ## # returns all of the sections for this version of the plan, and for the project's organisation # # @return [Array
,nil] either a list of sections, or nil if none were found - def sections - unless project.organisation.nil? then - sections = version.global_sections + project.organisation.all_sections(version_id) - else - sections = version.global_sections - end - return sections.uniq.sort_by &:number - end + def set_possible_guidance_groups + # find all the themes in this plan + # and get the guidance groups they belong to + ggroups = [] + self.template.phases.each do |phase| + phase.sections.each do |section| + section.questions.each do |question| + question.themes.each do |theme| + theme.guidances.each do |guidance| + ggroups << guidance.guidance_group + end + end + end + end + end + + self.guidance_groups = ggroups.uniq + end + + + ## # returns the guidances associated with the project's organisation, for a specified question # # @param question [Question] the question to find guidance for - # @return [Array] the list of guidances which pretain to the specified question - def guidance_for_question(question) - guidances = {} - # If project org isn't nil, get guidance by theme from any "non-subset" groups belonging to project org - unless project.organisation.nil? then - project.organisation.guidance_groups.each do |group| - if !group.optional_subset && (group.dmptemplates.pluck(:id).include?(project.dmptemplate_id) || group.dmptemplates.count == 0) then - group.guidances.each do |guidance| - guidance.themes.where("id IN (?)", question.theme_ids).each do |theme| - guidances = self.add_guidance_to_array(guidances, group, theme, guidance) - end - end - end - end - end - # Get guidance by theme from any guidance groups selected on creation - project.guidance_groups.each do |group| - if group.dmptemplates.pluck(:id).include?(project.dmptemplate_id) || group.dmptemplates.count == 0 then - group.guidances.each do |guidance| - guidance.themes.where("id IN (?)", question.theme_ids).each do |theme| - guidances = self.add_guidance_to_array(guidances, group, theme, guidance) - end - end - end - end - # Get guidance by question where guidance group was selected on creation or if group is organisation default - question.guidances.each do |guidance| - guidance.guidance_groups.each do |group| - if (group.organisation == project.organisation && !group.optional_subset) || project.guidance_groups.include?(group) then - guidances = self.add_guidance_to_array(guidances, group, nil, guidance) - end + # @return array of hashes with orgname, themes and the guidance itself + def guidance_for_question(question) + guidances = [] + + # add in the guidance for the template org + unless self.template.org.nil? then + self.template.org.guidance_groups.each do |group| + group.guidances.each do |guidance| + common_themes = guidance.themes.all & question.themes.all + if common_themes.length > 0 + guidances << { orgname: self.template.org.name, theme: common_themes.join(','), guidance: guidance } + end + end end - end + end + + # add in the guidance for the user's org + unless self.owner.nil? + unless self.owner.org.nil? then + self.owner.org.guidance_groups.each do |group| + group.guidances.each do |guidance| + common_themes = guidance.themes.all & question.themes.all + if common_themes.length > 0 + guidances << { orgname: self.template.org.name, theme: common_themes.join(','), guidance: guidance } + end + end + end + end + end - return guidances - end + # Get guidance by theme from any guidance groups currently selected + self.plan_guidance_groups.where(selected: true).each do |pgg| + group = pgg.guidance_group + group.guidances.each do |guidance| + common_themes = guidance.themes.all & question.themes.all + if common_themes.length > 0 + guidances << { orgname: self.template.org.name, theme: common_themes.join(','), guidance: guidance } + end + end + end + + return guidances + end ## # adds the given guidance to a hash indexed by a passed guidance group and theme @@ -178,60 +189,58 @@ # @param theme [Theme] the theme object for the GuidanceGroup # @param guidance [Guidance] the guidance object to be appended to the correct section of the array # @return [{GuidanceGroup => {Theme => Array}}] the updated object which was passed in - def add_guidance_to_array(guidance_array, guidance_group, theme, guidance) - if guidance_array[guidance_group].nil? then - guidance_array[guidance_group] = {} - end - if theme.nil? then - if guidance_array[guidance_group]["no_theme"].nil? then - guidance_array[guidance_group]["no_theme"] = [] - end - if !guidance_array[guidance_group]["no_theme"].include?(guidance) then - guidance_array[guidance_group]["no_theme"].push(guidance) - end - else - if guidance_array[guidance_group][theme].nil? then - guidance_array[guidance_group][theme] = [] - end - if !guidance_array[guidance_group][theme].include?(guidance) then - guidance_array[guidance_group][theme].push(guidance) - end - end + def add_guidance_to_array(guidance_array, guidance_group, theme, guidance) + if guidance_array[guidance_group].nil? then + guidance_array[guidance_group] = {} + end + if theme.nil? then + if guidance_array[guidance_group]["no_theme"].nil? then + guidance_array[guidance_group]["no_theme"] = [] + end + if !guidance_array[guidance_group]["no_theme"].include?(guidance) then + guidance_array[guidance_group]["no_theme"].push(guidance) + end + else + if guidance_array[guidance_group][theme].nil? then + guidance_array[guidance_group][theme] = [] + end + if !guidance_array[guidance_group][theme].include?(guidance) then + guidance_array[guidance_group][theme].push(guidance) + end + end return guidance_array - end + end ## # determines if the plan is editable by the specified user - # NOTE: This should be renamed to editable_by? # # @param user_id [Integer] the id for a user # @return [Boolean] true if user can edit the plan - def editable_by(user_id) - return project.editable_by(user_id) + def editable_by?(user_id) + role = roles.where(user_id: user_id).first + return role.present? && role.editor? end ## # determines if the plan is readable by the specified user - # NOTE: This shoudl be renamed to readable_by? + # TODO: introduce explicit readable rather than implicit + # currently role with no flags = readable # # @param user_id [Integer] the id for a user # @return [Boolean] true if the user can read the plan - def readable_by(user_id) - if project.nil? - return false - else - return project.readable_by(user_id) - end + def readable_by?(user_id) + role = roles.where(user_id: user_id).first + return role.present? end ## # determines if the plan is administerable by the specified user - # NOTE: This should be renamed to administerable_by? # # @param user_id [Integer] the id for the user # @return [Boolean] true if the user can administer the plan - def administerable_by(user_id) - return project.readable_by(user_id) + def administerable_by?(user_id) + role = roles.where(user_id: user_id).first + return role.present? && role.administrator? end @@ -251,7 +260,7 @@ "space_used" => 0 # percentage of available space in pdf used } - space_used = height_of_text(self.project.title, 2, 2) + space_used = height_of_text(self.title, 2, 2) sections.each do |s| space_used += height_of_text(s.title, 1, 1) @@ -274,124 +283,128 @@ "answer_id" => answer.id, "answer_created_at" => answer.created_at.to_i, "answer_text" => answer.text, - "answer_option_ids" => answer.option_ids, + "answer_option_ids" => answer.question_options.pluck(:id), "answered_by" => answer.user.name } q_format = q.question_format - status["num_answers"] += 1 if (q_format.title == I18n.t("helpers.checkbox") || q_format.title == I18n.t("helpers.multi_select_box") || + status["num_answers"] += 1 if (q_format.title == I18n.t("helpers.checkbox") || q_format.title == I18n.t("helpers.multi_select_box") || q_format.title == I18n.t("helpers.radio_buttons") || q_format.title == I18n.t("helpers.dropdown")) || answer.text.present? - section_answers += 1 - #TODO: include selected options in space estimate - else - status["questions"][q.id] = { - "answer_id" => nil, - "answer_created_at" => nil, - "answer_text" => nil, - "answer_option_ids" => nil, - "answered_by" => nil - } - end - status["sections"][s.id]["num_questions"] = section_questions - status["sections"][s.id]["num_answers"] = section_answers - end - end + section_answers += 1 + #TODO: include selected options in space estimate + else + status["questions"][q.id] = { + "answer_id" => nil, + "answer_created_at" => nil, + "answer_text" => nil, + "answer_option_ids" => nil, + "answered_by" => nil + } + end + status["sections"][s.id]["num_questions"] = section_questions + status["sections"][s.id]["num_answers"] = section_answers + end + end - status['space_used'] = estimate_space_used(space_used) - return status - end + status['space_used'] = estimate_space_used(space_used) + return status + end - +# TODO: Guessing this isn't in use since it still refers to Project and Version +=begin ## # defines and returns the details for the plan # details consists of a hash of: project_title, phase_title, and for each section, # section: title, question text for each question, answer type and answer value # # @return [Details] - def details - details = { - "project_title" => project.title, - "phase_title" => version.phase.title, - "sections" => {} - } - sections.sort_by(&:"number").each do |s| - details["sections"][s.number] = {} - details["sections"][s.number]["title"] = s.title - details["sections"][s.number]["questions"] = {} - s.questions.order("number").each do |q| - details["sections"][s.number]["questions"][q.number] = {} - details["sections"][s.number]["questions"][q.number]["question_text"] = q.text - answer = answer(q.id, false) - if ! answer.nil? then + def details + details = { + "project_title" => project.title, + "phase_title" => version.phase.title, + "sections" => {} + } + sections.sort_by(&:"number").each do |s| + details["sections"][s.number] = {} + details["sections"][s.number]["title"] = s.title + details["sections"][s.number]["questions"] = {} + s.questions.order("number").each do |q| + details["sections"][s.number]["questions"][q.number] = {} + details["sections"][s.number]["questions"][q.number]["question_text"] = q.text + answer = answer(q.id, false) + if ! answer.nil? then q_format = q.question_format - if (q_format.title == t("helpers.checkbox") || q_format.title == t("helpers.multi_select_box") || + if (q_format.title == t("helpers.checkbox") || q_format.title == t("helpers.multi_select_box") || q_format.title == t("helpers.radio_buttons") || q_format.title == t("helpers.dropdown")) then - details["sections"][s.number]["questions"][q.number]["selections"] = {} - answer.options.each do |o| - details["sections"][s.number]["questions"][q.number]["selections"][o.number] = o.text - end - end - details["sections"][s.number]["questions"][q.number]["answer_text"] = answer.text - end - end - end - return details - end + details["sections"][s.number]["questions"][q.number]["selections"] = {} + answer.options.each do |o| + details["sections"][s.number]["questions"][q.number]["selections"][o.number] = o.text + end + end + details["sections"][s.number]["questions"][q.number]["answer_text"] = answer.text + end + end + end + return details + end +=end +# TODO: commenting this old lock stuff out since PlanSection is gone and we wanted to get rid of it +=begin ## # determines wether or not a specified section of a plan is locked to a specified user and returns a status hash # # @param section_id [Integer] the setion to determine if locked # @param user_id [Integer] the user to determine if locked for # @return [Hash{String => Hash{String => Boolean, nil, String, Integer}}] - def locked(section_id, user_id) - plan_section = plan_sections.where("section_id = ? AND user_id != ? AND release_time > ?", section_id, user_id, Time.now).last - if plan_section.nil? then - status = { - "locked" => false, - "locked_by" => nil, - "timestamp" => nil, - "id" => nil - } - else - status = { - "locked" => true, - "locked_by" => plan_section.user.name, - "timestamp" => plan_section.updated_at, - "id" => plan_section.id - } - end - end + def locked(section_id, user_id) + plan_section = plan_sections.where("section_id = ? AND user_id != ? AND release_time > ?", section_id, user_id, Time.now).last + if plan_section.nil? then + status = { + "locked" => false, + "locked_by" => nil, + "timestamp" => nil, + "id" => nil + } + else + status = { + "locked" => true, + "locked_by" => plan_section.user.name, + "timestamp" => plan_section.updated_at, + "id" => plan_section.id + } + end + end ## # for each section, lock the section with the given user_id # # @param user_id [Integer] the id for the user who can use the sections - def lock_all_sections(user_id) - sections.each do |s| - lock_section(s.id, user_id, 1800) - end - end + def lock_all_sections(user_id) + sections.each do |s| + lock_section(s.id, user_id, 1800) + end + end ## # for each section, unlock the section # # @param user_id [Integer] the id for the user to unlock the sections for - def unlock_all_sections(user_id) - plan_sections.where(:user_id => user_id).order("created_at DESC").each do |lock| - lock.delete - end - end + def unlock_all_sections(user_id) + plan_sections.where(:user_id => user_id).order("created_at DESC").each do |lock| + lock.delete + end + end ## # for each section, unlock the section # Not sure how this is different from unlock_all_sections # # @param user_id [Integer] - def delete_recent_locks(user_id) - plan_sections.where(:user_id => user_id).each do |lock| - lock.delete - end - end + def delete_recent_locks(user_id) + plan_sections.where(:user_id => user_id).each do |lock| + lock.delete + end + end ## # Locks the specified section to only be used by the specified user, for the number of secconds specified @@ -400,23 +413,23 @@ # @param user_id [Integer] the id of the user who can use the section # @param release_time [Integer] the number of secconds the section will be locked for, defaults to 60 # @return [Boolean] wether or not the section was locked - def lock_section(section_id, user_id, release_time = 60) - status = locked(section_id, user_id) - if ! status["locked"] then - plan_section = PlanSection.new - plan_section.plan_id = id - plan_section.section_id = section_id - plan_section.release_time = Time.now + release_time.seconds - plan_section.user_id = user_id - plan_section.save - elsif status["current_user"] then - plan_section = PlanSection.find(status["id"]) - plan_section.release_time = Time.now + release_time.seconds - plan_section.save - else - return false - end - end + def lock_section(section_id, user_id, release_time = 60) + status = locked(section_id, user_id) + if ! status["locked"] then + plan_section = PlanSection.new + plan_section.plan_id = id + plan_section.section_id = section_id + plan_section.release_time = Time.now + release_time.seconds + plan_section.user_id = user_id + plan_section.save + elsif status["current_user"] then + plan_section = PlanSection.find(status["id"]) + plan_section.release_time = Time.now + release_time.seconds + plan_section.save + else + return false + end + end ## # unlocks the specified section for the specified user @@ -424,65 +437,614 @@ # @param section_id [Integer] the id for the section to be unlocked # @param user_id [Integer] the id for the user for whom the section was previously locked # @return [Boolean] wether or not the lock was removed - def unlock_section(section_id, user_id) - plan_sections.where(:section_id => section_id, :user_id => user_id).order("created_at DESC").each do |lock| - lock.delete - end - end + def unlock_section(section_id, user_id) + plan_sections.where(:section_id => section_id, :user_id => user_id).order("created_at DESC").each do |lock| + lock.delete + end + end +=end +# TODO: Commenting out because this method appears below as well so this one is overwritten +=begin ## # returns the time of either the latest answer to any question, or the latest update to the model # # @return [DateTime] the time at which the plan was last changed - def latest_update - if answers.any? then - last_answered = answers.order("updated_at DESC").first.updated_at - if last_answered > updated_at then - return last_answered - else - return updated_at - end - else - return updated_at - end - end + def latest_update + if answers.any? then + last_answered = answers.order("updated_at DESC").first.updated_at + if last_answered > updated_at then + return last_answered + else + return updated_at + end + else + return updated_at + end + end +=end +# TODO: Guessing this isn't in use since it still refers to Project and Version +=begin ## # returns an array of hashes. Each hash contains the question's id, the answer_id, # the answer_text, the answer_timestamp, and the answer_options # # @param section_id [Integer] the section to find answers of # @return [Array nil,String,Integer,DateTime}] - def section_answers(section_id) - section = Section.find(section_id) - section_questions = Array.new - counter = 0 - section.questions.each do |q| - section_questions[counter] = {} - section_questions[counter]["id"] = q.id - #section_questions[counter]["multiple_choice"] = q.multiple_choice - q_answer = answer(q.id, false) - if q_answer.nil? then - section_questions[counter]["answer_id"] = nil - if q.suggested_answers.find_by_organisation_id(project.organisation_id).nil? then - section_questions[counter]["answer_text"] = "" - else - section_questions[counter]["answer_text"] = q.default_value - end - section_questions[counter]["answer_timestamp"] = nil - section_questions[counter]["answer_options"] = Array.new - else - section_questions[counter]["answer_id"] = q_answer.id - section_questions[counter]["answer_text"] = q_answer.text - section_questions[counter]["answer_timestamp"] = q_answer.created_at - section_questions[counter]["answer_options"] = q_answer.options.pluck(:id) - end - counter = counter + 1 - end - return section_questions - end + def section_answers(section_id) + section = Section.find(section_id) + section_questions = Array.new + counter = 0 + section.questions.each do |q| + section_questions[counter] = {} + section_questions[counter]["id"] = q.id + #section_questions[counter]["multiple_choice"] = q.multiple_choice + q_answer = answer(q.id, false) + if q_answer.nil? then + section_questions[counter]["answer_id"] = nil + if q.suggested_answers.find_by_organisation_id(project.organisation_id).nil? then + section_questions[counter]["answer_text"] = "" + else + section_questions[counter]["answer_text"] = q.default_value + end + section_questions[counter]["answer_timestamp"] = nil + section_questions[counter]["answer_options"] = Array.new + else + section_questions[counter]["answer_id"] = q_answer.id + section_questions[counter]["answer_text"] = q_answer.text + section_questions[counter]["answer_timestamp"] = q_answer.created_at + section_questions[counter]["answer_options"] = q_answer.options.pluck(:id) + end + counter = counter + 1 + end + return section_questions + end +=end -private + + ## + # assigns the passed user_id to the creater_role for the project + # gives the user rights to read, edit, administrate, and defines them as creator + # + # @param user_id [Integer] the user to be given priveleges' id + def assign_creator(user_id) + add_user(user_id, true, true, true) + end + + + +# TODO: commenting these out because they are overriden by private methods below, so this +# is unreachable +=begin + ## + # Based on the height of the text gathered so far and the available vertical + # space of the pdf, estimate a percentage of how much space has been used. + # This is highly dependent on the layout in the pdf. A more accurate approach + # would be to render the pdf and check how much space had been used, but that + # could be very slow. + # NOTE: This is only an estimate, rounded up to the nearest 5%; it is intended + # for guidance when editing plan data, not to be 100% accurate. + # + # @param used_height [Integer] an estimate of the height used so far + # @return [Integer] the estimate of space used of an A4 portrain + def estimate_space_used(used_height) + @formatting ||= self.settings(:export).formatting + + return 0 unless @formatting[:font_size] > 0 + + margin_height = @formatting[:margin][:top].to_i + @formatting[:margin][:bottom].to_i + page_height = A4_PAGE_HEIGHT - margin_height # 297mm for A4 portrait + available_height = page_height * self.template.settings(:export).max_pages + + percentage = (used_height / available_height) * 100 + (percentage / ROUNDING).ceil * ROUNDING # round up to nearest five + end + + ## + # Take a guess at the vertical height (in mm) of the given text based on the + # font-size and left/right margins stored in the plan's settings. + # This assumes a fixed-width for each glyph, which is obviously + # incorrect for the font-face choices available; the idea is that + # they'll hopefully average out to that in the long-run. + # Allows for hinting different font sizes (offset from base via font_size_inc) + # and vertical margins (i.e. for heading text) + # + # @param text [String] the text to estimate size of + # @param font_size_inc [Integer] the size of the font of the text, defaults to 0 + # @param vertical_margin [Integer] the top margin above the text, defaults to 0 + def height_of_text(text, font_size_inc = 0, vertical_margin = 0) + @formatting ||= self.settings(:export).formatting + @margin_width ||= @formatting[:margin][:left].to_i + @formatting[:margin][:right].to_i + @base_font_size ||= @formatting[:font_size] + + return 0 unless @base_font_size > 0 + + font_height = FONT_HEIGHT_CONVERSION_FACTOR * (@base_font_size + font_size_inc) + font_width = font_height * FONT_WIDTH_HEIGHT_RATIO # Assume glyph width averages at 2/5s the height + leading = font_height / 2 + + chars_in_line = (A4_PAGE_WIDTH - @margin_width) / font_width # 210mm for A4 portrait + num_lines = (text.length / chars_in_line).ceil + + (num_lines * font_height) + vertical_margin + leading + end +=end + +# TODO: What are these used for? Should just be using self.org and self.org.funder? +=begin + ## + # sets a new funder for the project + # defaults to the first dmptemplate if the current template is nill and the funder has more than one dmptemplate + # + # @param new_funder_id [Integer] the id for a new funder + # @return [Organisation] the new funder + def funder_id=(new_funder_id) + if new_funder_id != "" then + new_funder = Org.find(new_funder_id); + if new_funder.templates.count >= 1 && self.template.nil? then + self.template = new_funder.templates.first + end + end + end + + ## + # returns the funder id for the plan + # + # @return [Integer, nil] the id for the funder + def funder_id + if self.template.nil? then + return nil + end + return self.template.org + end +=end + + ## + # returns the funder organisation for the project or nil if none is specified + # + # @return [Organisation, nil] the funder for project, or nil if none exists + def funder + template = self.template + if template.nil? then + return nil + end + + if template.customization_of + return template.customization_of.org + else + return template.org + end + end + +=begin + ## + # returns the name of the funder for the project + # + # @return [String] the name fo the funder for the project + def funder_name + if self.funder.nil? + return read_attribute(:funder_name) + else + return self.funder.name + end + end + + ## + # defines a new funder_name for the project. + # + # @param new_funder_name [String] the string name of the new funder + # @return [Integer, nil] the org_id of the new funder + def funder_name=(new_funder_name) + write_attribute(:funder_name, new_funder_name) + org_table = Org.arel_table + existing_org = Org.where(org_table[:name].matches(new_funder_name)) + if existing_org.nil? + existing_org = Org.where(org_table[:abbreviation].matches(new_funder_name)) + end + unless existing_org.empty? + self.funder_id=existing_org.id + end + end + + ## + # sets a new institution_id if there is no current organisation + # + # @param new_institution_id [Integer] the id for the new institution + # @return [Integer, Bool] false if an organisation exists, or the id of the set org if a new organisation is set + def institution_id=(new_institution_id) + if organisation.nil? then + self.organisation_id = new_institution_id + end + end + + ## + # returns the organisation which is root over the owning organisation + # + # @return [Integer, nil] the organisation_id or nil + def institution_id +# if organisation.nil? +# return nil +# else +# return organisation.root.id +# end + return template.org.id + end + + ## + # defines a new organisation_id for the project + # but is confusingly labled unit_id + # + # @param new_unit_id [Integer] + # @return [Integer, Boolean] the new organisation ID or false if no unit_id was passed + def unit_id=(new_unit_id) + unless new_unit_id.nil? ||new_unit_id == "" + self.organisation_id = new_unit_id + end + end + + ## + # returns the organisation_id or nil + # again seems redundant + # + # @return [nil, Integer] nil if no organisation, or the id if there is an organisation specified + def unit_id + if organisation.nil? || organisation.parent_id.nil? + return nil + else + return organisation_id + end + end +=end + + ## + # assigns the passed user_id as an editor for the project + # gives the user rights to read and edit + # + # @param user_id [Integer] the user to be given priveleges' id + def assign_editor(user_id) + add_user(user_id, true) + end + + ## + # assigns the passed user_id as a reader for the project + # gives the user rights to read + # + # @param user_id [Integer] the user to be given priveleges' id + def assign_reader(user_id) + add_user(user_id) + end + + ## + # assigns the passed user_id as an administrator for the project + # gives the user rights to read, adit, and administrate the project + # + # @param user_id [Integer] the user to be given priveleges' id + def assign_administrator(user_id) + add_user(user_id, true, true) + end + +# TODO: ProjectGroup doesn't exist anymore so commenting these out +=begin + ## + # returns the projects which the user can atleast read + # + # @param user_id [Integer] the user to lookup projects for + # @return [Array] list of all projects the user can atleast read + def self.projects_for_user(user_id) + projects = Array.new + groups = ProjectGroup.where("user_id = ?", user_id) + unless groups.nil? then + groups.each do |group| + unless group.project.nil? then + projects << group.project + end + end + end + return projects + end + + ## + # whether or not the specified user_id created this project + # should be renamed to created_by? + # + # @param user_id [Integer] the user to check the priveleges of + # @return [Boolean] true if the user created the project + def created_by(user_id) + user = project_groups.find_by_user_id(user_id) + if (! user.nil?) && user.project_creator then + return true + else + return false + end + end +=end + + ## + # the datetime for the latest update of this plan + # + # @return [DateTime] the time of latest update + def latest_update + latest_update = updated_at + phases.each do |phase| + if phase.updated_at > latest_update then + latest_update = phase.updated_at + end + end + return latest_update + end + + # Getters to match 'My plans' columns + + ## + # the title of the project + # + # @return [String] the title of the project + def name + self.title + end + + ## + # the owner of the project + # + # @return [User] the creater of the project + def owner + self.roles.each do |role| + if role.creator? + return role.user + end + end + return nil + end + + ## + # the time the project was last updated, formatted as a date + # + # @return [Date] last update as a date + def last_edited + self.latest_update.to_date + end + +# TODO: These next 2 reference defunct models so commenting out +=begin + ## + # whether or not the plan is shared with anybody + # + # @return [Boolean] true if the project has been shared + def shared? + self.project_groups.count > 1 + end + + alias_method :shared, :shared? + + ## + # the organisation who owns the project + # + # @return [Dmptemplate,Organisation,String] the template, it's owner, or it's owner's abreviation + def template_owner + self.dmptemplate.try(:organisation).try(:abbreviation) + end +=end + + + + ## + # + # The following method is to help optimise data access. + # Even using includes or joins the data access gets performed lazily + # and the caching appears to be forgotten at times over view/partial boundaries + # To get round it we can pull everything out into a hash and use that + # which guarantees no further DB accesses. + # + # The serializable_hash method only pulls in one level but this includes + # attributes which are "through" other attributes. So we do a basic + # conversion to hash and then a "fixup" which knits the pieces together into + # the structure which we really want. + # + def to_hash + plan_data = self.serializable_hash( + include: [ :template, :phases, :sections, + :answers, :notes, :roles, :users, :questions, + :plan_guidance_groups, :guidance_groups] + ) + + question_hash = {} + + plan_data["questions"].each do |q| + question_hash[q["id"]] = q + end + + question_ids = question_hash.keys + + suggested_answers = SuggestedAnswer.where(question_id: question_ids).where.not(text: '') + suggested_answers.each do |sa| + question_hash[sa.question_id]["suggested_answer"] = sa.serializable_hash + end + + qf_hash = {} + QuestionFormat.all.each do |qf| + qf_hash[qf.id] = qf.serializable_hash + end + question_hash.values.each do |q| + q["question_format"] = qf_hash[q["question_format_id"]] + end + + gg_ids = plan_data["plan_guidance_groups"].select{|pgg| pgg["selected"]}.map{|pgg| pgg["guidance_group_id"]} + gg_hash = {} + + theme_guidance = {} + + ggs = GuidanceGroup.find(gg_ids).each do |gg| + gg_hash[gg.id] = gg.serializable_hash + end + + guidances = Guidance.joins(:themes).select('guidances.guidance_group_id, guidances.text, themes.title').where(guidance_group: gg_ids).to_a + guidances.each do |g| + title = g.title + if !theme_guidance.has_key?(title) + theme_guidance[title] = Array.new + end + theme_guidance[title] << { + "text" => g.text, + "org" => gg_hash[g.guidance_group_id]["name"] + } + end + + plan_data["questions"].each do |q| + qg = {} + if q.has_key?("themes") + q["themes"].each do |t| + title = t["title"] + qg[title] = theme_guidance[title] if theme_guidance.has_key?(title) + end + q["theme_guidance"] = qg + end + end + + fixup_hash(plan_data) + + return plan_data + end + + + + private + + # reconnect the various parts of the hash so that: + # plan = { + # template: + # phases: + # sections: + # questions: + # answers + # } + # + # becomes a nested structure like so + # + # plan = { template: { + # phases: [ { + # sections: [ { + # questions: [ { + # answers: [...] + # + def fixup_hash(plan) + # sort out guidance first so we can add it to the questions + # before rolling up + ghash = {} + plan["guidance_groups"].map{|g| ghash[g["id"]] = g} + plan["plan_guidance_groups"].each do |pgg| + pgg["guidance_group"] = ghash[ pgg["guidance_group_id"] ] + end + + rollup(plan, "notes", "answer_id", "answers") + rollup(plan, "answers", "question_id", "questions") + rollup(plan, "questions", "section_id", "sections", true) + rollup(plan, "sections", "phase_id", "phases", true) + + plan["template"]["phases"] = plan.delete("phases") + plan["template"]["phases"].sort! { |x,y| x["number"].to_i <=> y["number"].to_i } + + plan["template"]["org"] = Org.find(plan["template"]["org_id"]).serializable_hash() + + # when editing phases we want the number of questions answered + # so calculate that now + plan["template"]["phases"].each do |phase| + phase["sections"].each do |section| + nanswers = 0 + section["questions"].each do |question| + if question.has_key?("answers") && question["answers"].first["text"].present? + nanswers += 1 + end + end + section["nanswers"] = nanswers + end + end + + end + + + # find all object under src_plan_key + # merge them into the items under obj_plan_key using + # super_id = id + # so we have answers which each have a question_id + # rollup(plan, "answers", "quesiton_id", "questions") + # will put the answers into the right questions. + # sort determines whether the items being rolled up should be sorted by number field + def rollup(plan, src_plan_key, super_id, obj_plan_key, sort = false) + id_to_obj = Hash.new() + plan[src_plan_key].each do |o| + id = o[super_id] + if !id_to_obj.has_key?(id) + id_to_obj[id] = Array.new + end + id_to_obj[id] << o + end + + plan[obj_plan_key].each do |o| + id = o["id"] + if id_to_obj.has_key?(id) + if sort + id_to_obj[ id ].sort! { |x,y| x["number"].to_i <=> y["number"].to_i } + end + o[src_plan_key] = id_to_obj[ id ] + end + end + plan.delete(src_plan_key) + end + + ## + # adds a user to the project + # if no flags are specified, the user is given read privleges + # + # @param user_id [Integer] the user to be given privleges + # @param is_editor [Boolean] whether or not the user can edit the project + # @param is_administrator [Boolean] whether or not the user can administrate the project + # @param is_creator [Boolean] wheter or not the user created the project + # @return [Array] + # + # TODO: change this to specifying uniqueness of user/plan association and handle + # that way + # + def add_user(user_id, is_editor = false, is_administrator = false, is_creator = false) + Role.where(plan_id: self.id, user_id: user_id).each do |r| + r.destroy + end + + role = Role.new + role.user_id = user_id + role.plan_id = id + + role.creator= is_creator + role.editor= is_editor + role.administrator= is_administrator + role.save + + # This is necessary because we're creating the associated record but not assigning it + # to roles. Auto-saving like this may be confusing when coding upstream in a controller, + # view or api. Should probably change this to: + # self.roles << role + # and then let the save be called manually via: + # plan.save! + #self.reload + end + + ## + # creates a plan for each phase in the dmptemplate associated with this project + # unless the phase is unpublished, it creates a new plan, and a new version of the plan and adds them to the project's plans + # + # @return [Array] + def create_plans + dmptemplate.phases.each do |phase| + latest_published_version = phase.latest_published_version + unless latest_published_version.nil? + new_plan = Plan.new + new_plan.version = latest_published_version + plans << new_plan + end + end + end + + ## # Based on the height of the text gathered so far and the available vertical @@ -537,341 +1099,4 @@ (num_lines * font_height) + vertical_margin + leading end - - - # BEGIN METHODS FROM PROJECT - # - - - - ## - # sets a new funder for the project - # defaults to the first dmptemplate if the current template is nill and the funder has more than one dmptemplate - # - # @param new_funder_id [Integer] the id for a new funder - # @return [Organisation] the new funder - def funder_id=(new_funder_id) - if new_funder_id != "" then - new_funder = Org.find(new_funder_id); - if new_funder.dmptemplates.count >= 1 && self.dmptemplate.nil? then - self.dmptemplate = new_funder.dmptemplates.first - end - end - end - - ## - # returns the funder id for the project - # - # @return [Integer, nil] the id for the funder - def funder_id - if self.dmptemplate.nil? then - return nil - end - template_org = self.dmptemplate.organisation - if template_org.organisation_type.name == constant("organisation_types.funder").downcase - return template_org.id - else - return nil - end - end - - ## - # returns the funder organisation for the project or nil if none is specified - # - # @return [Organisation, nil] the funder for project, or nil if none exists - def funder - if self.dmptemplate.nil? then - return nil - end - template_org = self.dmptemplate.organisation - if template_org.organisation_type.name == constant("organisation_types.funder").downcase - return template_org - else - return nil - end - end - - ## - # returns the name of the funder for the project - # - # @return [String] the name fo the funder for the project - def funder_name - if self.funder.nil? - return read_attribute(:funder_name) - else - return self.funder.name - end - end - - ## - # defines a new funder_name for the project. - # - # @param new_funder_name [String] the string name of the new funder - # @return [Integer, nil] the org_id of the new funder - def funder_name=(new_funder_name) - write_attribute(:funder_name, new_funder_name) - org_table = Org.arel_table - existing_org = Org.where(org_table[:name].matches(new_funder_name)) - if existing_org.nil? - existing_org = Org.where(org_table[:abbreviation].matches(new_funder_name)) - end - unless existing_org.empty? - self.funder_id=existing_org.id - end - end - - ## - # sets a new institution_id if there is no current organisation - # - # @param new_institution_id [Integer] the id for the new institution - # @return [Integer, Bool] false if an organisation exists, or the id of the set org if a new organisation is set - def institution_id=(new_institution_id) - if organisation.nil? then - self.organisation_id = new_institution_id - end - end - - ## - # returns the organisation which is root over the owning organisation - # - # @return [Integer, nil] the organisation_id or nil - def institution_id - if organisation.nil? - return nil - else - return organisation.root.id - end - end - - ## - # defines a new organisation_id for the project - # but is confusingly labled unit_id - # - # @param new_unit_id [Integer] - # @return [Integer, Boolean] the new organisation ID or false if no unit_id was passed - def unit_id=(new_unit_id) - unless new_unit_id.nil? ||new_unit_id == "" - self.organisation_id = new_unit_id - end - end - - ## - # returns the organisation_id or nil - # again seems redundant - # - # @return [nil, Integer] nil if no organisation, or the id if there is an organisation specified - def unit_id - if organisation.nil? || organisation.parent_id.nil? - return nil - else - return organisation_id - end - end - - ## - # assigns the passed user_id to the creater_role for the project - # gives the user rights to read, edit, administrate, and defines them as creator - # - # @param user_id [Integer] the user to be given priveleges' id - def assign_creator(user_id) - add_user(user_id, true, true, true) - end - - ## - # assigns the passed user_id as an editor for the project - # gives the user rights to read and edit - # - # @param user_id [Integer] the user to be given priveleges' id - def assign_editor(user_id) - add_user(user_id, true) - end - - ## - # assigns the passed user_id as a reader for the project - # gives the user rights to read - # - # @param user_id [Integer] the user to be given priveleges' id - def assign_reader(user_id) - add_user(user_id) - end - - ## - # assigns the passed user_id as an administrator for the project - # gives the user rights to read, adit, and administrate the project - # - # @param user_id [Integer] the user to be given priveleges' id - def assign_administrator(user_id) - add_user(user_id, true, true) - end - - ## - # whether or not the current plan is administrable by the user - # - # @param user_id [Integer] the user to check if has privleges - # @return [Boolean] true if user can administer project, false otherwise - def administerable_by(user_id) - user = project_groups.find_by_user_id(user_id) - if (! user.nil?) && user.project_administrator then - return true - else - return false - end - end - - ## - # whether or not the current plan is editable by the user - # - # @param user_id [Integer] the user to check if has privleges - # @return [Boolean] true if user can edit project, false otherwise - def editable_by(user_id) - user = project_groups.find_by_user_id(user_id) - if (! user.nil?) && user.project_editor then - return true - else - return false - end - end - - ## - # whether or not the current plan is readable by the user - # should be renamed to readable_by? - # - # @param user_id [Integer] the user to check if has privleges - # @return [Boolean] true if user can read project, false otherwise - def readable_by(user_id) - user = project_groups.find_by_user_id(user_id) - if (! user.nil?) then - return true - else - return false - end - end - - ## - # returns the projects which the user can atleast read - # - # @param user_id [Integer] the user to lookup projects for - # @return [Array] list of all projects the user can atleast read - def self.projects_for_user(user_id) - projects = Array.new - groups = ProjectGroup.where("user_id = ?", user_id) - unless groups.nil? then - groups.each do |group| - unless group.project.nil? then - projects << group.project - end - end - end - return projects - end - - ## - # whether or not the specified user_id created this project - # should be renamed to created_by? - # - # @param user_id [Integer] the user to check the priveleges of - # @return [Boolean] true if the user created the project - def created_by(user_id) - user = project_groups.find_by_user_id(user_id) - if (! user.nil?) && user.project_creator then - return true - else - return false - end - end - - ## - # the datetime for the latest update of this project, or any plan it owns - # - # @return [DateTime] the time of latest update - def latest_update - latest_update = updated_at - plans.each do |plan| - if plan.latest_update > latest_update then - latest_update = plan.latest_update - end - end - return latest_update - end - - # Getters to match 'My plans' columns - - ## - # the title of the project - # - # @return [String] the title of the project - def name - self.title - end - - ## - # the owner of the project - # - # @return [User] the creater of the project - def owner - self.project_groups.find_by_project_creator(true).try(:user) - end - - ## - # the time the project was last updated, formatted as a date - # - # @return [Date] last update as a date - def last_edited - self.latest_update.to_date - end - - ## - # whether or not the plan is shared with anybody - # - # @return [Boolean] true if the project has been shared - def shared? - self.project_groups.count > 1 - end - - alias_method :shared, :shared? - - ## - # the organisation who owns the project - # - # @return [Dmptemplate,Organisation,String] the template, it's owner, or it's owner's abreviation - def template_owner - self.dmptemplate.try(:organisation).try(:abbreviation) - end - - private - - ## - # adds a user to the project - # if no flags are specified, the user is given read privleges - # - # @param user_id [Integer] the user to be given privleges - # @param is_editor [Boolean] whether or not the user can edit the project - # @param is_administrator [Boolean] whether or not the user can administrate the project - # @param is_creator [Boolean] wheter or not the user created the project - # @return [Array] - def add_user(user_id, is_editor = false, is_administrator = false, is_creator = false) - group = ProjectGroup.new - group.user_id = user_id - group.project_creator = is_creator - group.project_editor = is_editor - group.project_administrator = is_administrator - project_groups << group - end - - ## - # creates a plan for each phase in the dmptemplate associated with this project - # unless the phase is unpublished, it creates a new plan, and a new version of the plan and adds them to the project's plans - # - # @return [Array] - def create_plans - dmptemplate.phases.each do |phase| - latest_published_version = phase.latest_published_version - unless latest_published_version.nil? - new_plan = Plan.new - new_plan.version = latest_published_version - plans << new_plan - end - end - end - end diff --git a/app/models/plan_guidance_group.rb b/app/models/plan_guidance_group.rb new file mode 100644 index 0000000..7b76499 --- /dev/null +++ b/app/models/plan_guidance_group.rb @@ -0,0 +1,11 @@ +# Used to link plans to guidance groups +# the links are created at plan creation stage +# and link to all possible GGs +# then the selected field keeps track of which ones the user has turned on /off +# +class PlanGuidanceGroup < ActiveRecord::Base + belongs_to :plan + belongs_to :guidance_group + + attr_accessible :selected +end diff --git a/app/models/question.rb b/app/models/question.rb index f7db830..61eddd1 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -25,8 +25,9 @@ :question_options_attributes, :suggested_answers_attributes, :option_comment_display, :theme_ids, :section, :question_format, :question_options, :suggested_answers, :answers, :themes, - :modifiable, :as => [:default, :admin] + :modifiable, :option_comment_display, :as => [:default, :admin] + validates :text, :section, :number, presence: true # EVALUATE CLASS AND INSTANCE METHODS BELOW @@ -43,69 +44,47 @@ "#{text}" end - def select_text - cleantext = text.gsub(/<[^<]+>/, '') - if cleantext.length > 120 - cleantext = cleantext.slice(0,120) + ## + # deep copy the given question and all it's associations + # + # @params [Question] question to be deep copied + # @return [Question] the saved, copied question + def self.deep_copy(question) + question_copy = question.dup + question_copy.save! + question.question_options.each do |question_option| + question_option_copy = QuestionOption.deep_copy(question_option) + question_option_copy.question_id = question_copy.id + question_option_copy.save! end - cleantext + question.suggested_answers.each do |suggested_answer| + suggested_answer_copy = SuggestedAnswer.deep_copy(suggested_answer) + suggested_answer_copy.question_id = question_copy.id + suggested_answer_copy.save! + end + question.themes.each do |theme| + question_copy.themes << theme + end + return question_copy end - amoeba do - include_association :options - include_association :suggested_answers - clone [:themes] - end - - #def question_type? - # type_label = {} - # if self.is_text_field? - # type_label = 'Text field' - # elsif self.multiple_choice? - # type_label = 'Multiple choice' - # else - # type_label = 'Text area' - # end - # return type_label - #end - ## - # for each question theme, appends them separated by comas - # shouldnt have a ? after the method name + # guidance for org # - # @return [Hash{String=> String}] - def question_themes? - themes_label = {} - i = 1 - themes_quest = self.themes - - themes_quest.each do |tt| - themes_label = tt.title - - if themes_quest.count > i then - themes_label += ',' - i +=1 - end - end - - return themes_label - end - - ## - # guidance for question in the org admin - # - # @param question [Question] the question to find guidance for - # @param org_admin [Organisation] the organisation to find guidance for + # @param org [Org] the org to find guidance for # @return [Hash{String => String}] - def guidance_for_question(question, org) + def guidance_for_org(org) # pulls together guidance from various sources for question guidances = {} - theme_ids = question.theme_ids - - GuidanceGroup.where(org_id: org.id).each do |group| - group.guidances.each do |g| - g.themes.where("id IN (?)", theme_ids).each do |gg| - guidances["#{group.name} " + I18n.t('admin.guidance_lowercase_on') + " #{gg.title}"] = g + theme_ids = themes.collect{|t| t.id} + if theme_ids.present? + GuidanceGroup.includes(guidances: :themes).where(org_id: org.id).each do |group| + group.guidances.each do |g| + g.themes.each do |theme| + if theme_ids.include? theme.id + guidances["#{group.name} " + I18n.t('admin.guidance_lowercase_on') + " #{theme.title}"] = g + end + end end end end diff --git a/app/models/question_format.rb b/app/models/question_format.rb index fb4dbb2..7f1a900 100644 --- a/app/models/question_format.rb +++ b/app/models/question_format.rb @@ -9,7 +9,7 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :title, :description, :as => [:default, :admin] + attr_accessible :title, :description, :option_based, :questions, :as => [:default, :admin] # EVALUATE CLASS AND INSTANCE METHODS BELOW diff --git a/app/models/question_option.rb b/app/models/question_option.rb index c11151a..11552b0 100644 --- a/app/models/question_option.rb +++ b/app/models/question_option.rb @@ -7,5 +7,19 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :text, :question_id, :is_default, :number, :question, :as => [:default, :admin] + attr_accessible :text, :question_id, :is_default, :number, :question, + :as => [:default, :admin] + + validates :text, :question, :number, presence: true + + ## + # deep copy the given question_option and all it's associations + # + # @params [QuestionOption] question_option to be deep copied + # @return [QuestionOption] the saved, copied question_option + def self.deep_copy(question_option) + question_option_copy = question_option.dup + question_option_copy.save! + return question_option_copy + end end diff --git a/app/models/region.rb b/app/models/region.rb index 8147fed..2c57e79 100644 --- a/app/models/region.rb +++ b/app/models/region.rb @@ -1,3 +1,8 @@ class Region < ActiveRecord::Base - + has_many :sub_regions, class_name: 'Region', foreign_key: 'super_region_id' + + belongs_to :super_region, class_name: 'Region' + + validates :name, presence: true, uniqueness: true + validates :abbreviation, uniqueness: true, allow_nil: true end \ No newline at end of file diff --git a/app/models/region_group.rb b/app/models/region_group.rb deleted file mode 100644 index 8f83a83..0000000 --- a/app/models/region_group.rb +++ /dev/null @@ -1,3 +0,0 @@ -class RegionGroup < ActiveRecord::Base - -end \ No newline at end of file diff --git a/app/models/role.rb b/app/models/role.rb index 282802d..cf49c87 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -1,6 +1,8 @@ class Role < ActiveRecord::Base include FlagShihTzu + before_validation :check_access_level + ## # Associations belongs_to :user @@ -14,36 +16,8 @@ 3 => :editor, column: 'access' - - - # EVALUATE CLASS AND INSTANCE METHODS BELOW - # - # What do they do? do they do it efficiently, and do we need them? - # These functions are from the old project_groups model - - - - ## - # returns the user's email unless it is nil - # - # @return [Boolean, String] false if no email exists, the email otherwise - def email - unless user.nil? - return user.email - end - end - - ## - # define a new user for the project group by email - # - # @param new_email [String] the email of the new user for the project group - # @return [User] the new user - def email=(new_email) - unless User.find_by(email: email).nil? then - user = User.find_by(email: email) - end - self.save! - end + validates :user, :plan, :access, presence: true + validates :access, numericality: {greater_than: 0} ## # return the access level for the current project group @@ -60,7 +34,6 @@ else return 1 end - self.save! end ## @@ -81,6 +54,11 @@ else self.editor = false end - self.save! + self.creator = true unless self.administrator? || self.editor? + end + + # Ensures that the access attribute is set (will default to creator - see logic in access_level=) + def check_access_level + self.access_level = self.access_level end end diff --git a/app/models/section.rb b/app/models/section.rb index 34acdbe..87e98b6 100644 --- a/app/models/section.rb +++ b/app/models/section.rb @@ -10,8 +10,11 @@ accepts_nested_attributes_for :questions, :reject_if => lambda {|a| a[:text].blank? }, :allow_destroy => true # accepts_nested_attributes_for :version - attr_accessible :organisation_id, :description, :number, :title, :published, :questions_attributes, - :organisation, :modifiable, :phase, :as => [:default, :admin] + attr_accessible :phase_id, :description, :number, :title, :published, + :questions_attributes, :organisation, :phase, :modifiable, + :as => [:default, :admin] + + validates :phase, :title, :number, presence: true ## # return the title of the section @@ -21,8 +24,20 @@ "#{title}" end - amoeba do - include_association :questions + ## + # deep copy of the given section and all it's associations + # + # @params [Section] section to be deep copied + # @return [Section] the saved, copied section + def self.deep_copy(section) + section_copy = section.dup + section_copy.save! + section.questions.each do |question| + question_copy = Question.deep_copy(question) + question_copy.section_id = section_copy.id + question_copy.save! + end + return section_copy end end diff --git a/app/models/settings/plan_list.rb b/app/models/settings/plan_list.rb index 812e2c8..26f6ed5 100644 --- a/app/models/settings/plan_list.rb +++ b/app/models/settings/plan_list.rb @@ -10,7 +10,7 @@ validate do cols = value["columns"] - + if cols.present? # columns can be empty, in which case they revert to defaults errors.add(:columns, I18n.t("helpers.settings.projects.errors.no_name")) unless cols.member?("name") errors.add(:columns, I18n.t("helpers.settings.projects.errors.duplicate")) unless cols.keys.uniq == cols.keys diff --git a/app/models/suggested_answer.rb b/app/models/suggested_answer.rb index b4df355..91ce3aa 100644 --- a/app/models/suggested_answer.rb +++ b/app/models/suggested_answer.rb @@ -8,9 +8,12 @@ ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :org_id, :question_id, :text, :is_example, :as => [:default, :admin] + attr_accessible :org_id, :question_id, :text, :is_example, + :org, :question, :as => [:default, :admin] + validates :question, :org, presence: true + # EVALUATE CLASS AND INSTANCE METHODS BELOW # # What do they do? do they do it efficiently, and do we need them? @@ -25,4 +28,15 @@ "#{text}" end + + ## + # deep copy the given question_option and all it's associations + # + # @params [QuestionOption] question_option to be deep copied + # @return [QuestionOption] the saved, copied question_option + def self.deep_copy(suggested_answer) + suggested_answer_copy = suggested_answer.dup + suggested_answer_copy.save! + return suggested_answer_copy + end end \ No newline at end of file diff --git a/app/models/template.rb b/app/models/template.rb index 510eb9b..9c59637 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -5,28 +5,47 @@ # Associations belongs_to :org has_many :plans - has_many :phases + has_many :phases, dependent: :destroy has_many :sections, through: :phases has_many :questions, through: :sections + has_many :customizations, class_name: 'Template', foreign_key: 'dmptemplate_id' + belongs_to :dmptemplate, class_name: 'Template' + ## # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 - attr_accessible :id, :organisation_id, :description, :published, :title, :locale, :is_default, - :guidance_group_ids, :organisation, :plans, :phases, :org, :as => [:default, :admin] + attr_accessible :id, :org_id, :description, :published, :title, :locale, + :is_default, :guidance_group_ids, :org, :plans, :phases, + :version, :visibility, :published, :as => [:default, :admin] # defines the export setting for a template object has_settings :export, class_name: 'Settings::Template' do |s| s.key :export, defaults: Settings::Template::DEFAULT_SETTINGS end - + validates :org, :title, :version, presence: true # EVALUATE CLASS AND INSTANCE METHODS BELOW # # What do they do? do they do it efficiently, and do we need them? + ## + # deep copy the given template and all of it's associations + # + # @params [Template] template to be deep copied + # @return [Template] saved copied template + def self.deep_copy(template) + template_copy = template.dup + template_copy.save! + template.phases.each do |phase| + phase_copy = Phase.deep_copy(phase) + phase_copy.template_id = template_copy.id + phase_copy.save! + end + return template_copy + end ## # takes a type or organisation and returns all published templates from @@ -34,6 +53,7 @@ # # @param ot [String] name of an organisation type e.g. founder # @return [Array] list of published dmptemplates +=begin def self.templates_org_type(ot) # DISCUSS - This function other than the check for the template being published # is a superclass for the below funders_templates @@ -78,7 +98,7 @@ new_templates = self.where("org_id = ?", org_id) return new_templates end - + ## # returns an array with all funders and of the given organisations's # institutional templates @@ -104,7 +124,7 @@ return templates_list end - + ## # Returns the string name of the organisation type of the organisation who # owns this dmptemplate @@ -114,7 +134,9 @@ org_type = org.organisation_type return org_type end - +=end + +# TODO: Why are we passing in an org and template here? ## # Verify if a template has customisation by given organisation # @@ -124,7 +146,7 @@ def has_customisations?(org_id, temp) modifiable = true phases.each do |phase| - modifiable = modifable && phase.modifiable + modifiable = modifiable && phase.modifiable end return !modifiable # if temp.org_id != org_id then @@ -141,6 +163,7 @@ # end end +=begin ## # verify if there are any publish version for the template # @@ -151,8 +174,8 @@ end return false end - - +=end + # OLD CODE STARTS HERE end diff --git a/app/models/theme.rb b/app/models/theme.rb index 11cc32e..315dfe8 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -13,6 +13,8 @@ attr_accessible :description, :title, :locale , :as => [:default, :admin] + validates :title, presence: true + # EVALUATE CLASS AND INSTANCE METHODS BELOW # # What do they do? do they do it efficiently, and do we need them? diff --git a/app/models/token_permission_type.rb b/app/models/token_permission_type.rb index a7362d9..71f8186 100644 --- a/app/models/token_permission_type.rb +++ b/app/models/token_permission_type.rb @@ -2,7 +2,8 @@ ## # Associations #has_and_belongs_to_many :org_token_permissions, join_table: "org_token_permissions" - has_and_belongs_to_many :organisations, join_table: 'org_token_permissions', unique: true +# has_and_belongs_to_many :organisations, join_table: 'org_token_permissions', unique: true + has_and_belongs_to_many :orgs, join_table: 'org_token_permissions', unique: true ## # Possibly needed for active_admin diff --git a/app/models/user.rb b/app/models/user.rb index 1a3df5c..4cf42a2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,8 +6,9 @@ # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable - devise :invitable, :database_authenticatable, :registerable, :recoverable, :rememberable, - :trackable, :validatable, :confirmable, :omniauthable, :omniauth_providers => [:shibboleth] + devise :invitable, :database_authenticatable, :registerable, :recoverable, + :rememberable, :trackable, :validatable, :confirmable, :omniauthable, + :omniauth_providers => [:shibboleth, :orcid] ## # Associations @@ -16,8 +17,9 @@ belongs_to :org has_many :answers has_many :notes + has_many :exported_plans has_many :roles, dependent: :destroy - has_many :projects, through: :roles do + has_many :plans, through: :roles do def filter(query) return self unless query.present? t = self.arel_table @@ -39,11 +41,13 @@ # Possibly needed for active_admin # -relies on protected_attributes gem as syntax depricated in rails 4.2 accepts_nested_attributes_for :roles - attr_accessible :password_confirmation, :encrypted_password, :remember_me, :id, :email, - :firstname, :last_login,:login_count, :orcid_id, :password, :shibboleth_id, - :user_status_id, :surname, :user_type_id, :org_id, :skip_invitation, - :other_organisation, :accept_terms, :role_ids, :dmponline3, :api_token, - :organisation, :language, :language_id, :org, :perms, :confirmed_at + attr_accessible :password_confirmation, :encrypted_password, :remember_me, + :id, :email, :firstname, :last_login,:login_count, :orcid_id, + :password, :shibboleth_id, :user_status_id, :surname, + :user_type_id, :org_id, :skip_invitation, :other_organisation, + :accept_terms, :role_ids, :dmponline3, :api_token, + :organisation, :language, :language_id, :org, :perms, + :confirmed_at, :org_id validates :email, email: true, allow_nil: true, uniqueness: true @@ -72,7 +76,7 @@ # @param user_email [Boolean] defaults to true, allows the use of email if there is no firstname or surname # @return [String] the email or the firstname and surname of the user def name(use_email = true) - if ((firstname.nil? && surname.nil?) || (firstname.strip == "" && surname.strip == "")) && use_email then + if (firstname.blank? && surname.blank?) || use_email then return email else name = "#{firstname} #{surname}" @@ -89,6 +93,9 @@ user_identifiers.where(identifier_scheme: scheme).first end +# TODO: Check the logic here. Its deleting the permissions if the user does not have permission +# to change orgs and either the incoming or existing org is nil. +# We should also NOT be auto-saving here!!! ## # sets a new organisation id for the user # if the user has any perms such as org_admin or admin, those are removed @@ -96,6 +103,7 @@ # # @param new_organisation_id [Integer] the id for an organisation # @return [String] the empty string as a causality of setting api_token +=begin def organisation_id=(new_organisation_id) unless self.can_change_org? || new_organisation_id.nil? || self.organisation.nil? # rip all permissions from the user @@ -115,7 +123,8 @@ def organisation=(new_organisation) organisation_id = new_organisation.id unless new_organisation.nil? end - +=end + ## # checks if the user is a super admin # if the user has any privelege which requires them to see the super admin page @@ -123,7 +132,7 @@ # # @return [Boolean] true if the user is an admin def can_super_admin? - return self.can_add_orgs? || self.can_grant_api_to_orgs? || can_change_org? + return self.can_add_orgs? || self.can_grant_api_to_orgs? || self.can_change_org? end ## @@ -133,7 +142,8 @@ # # @return [Boolean] true if the user is an organisation admin def can_org_admin? - return self.can_grant_permissions? || self.can_modify_guidance? || self.can_modify_templates? || self.can_modify_org_details? + return self.can_grant_permissions? || self.can_modify_guidance? || + self.can_modify_templates? || self.can_modify_org_details? end ## @@ -141,7 +151,7 @@ # # @return [Boolean] true if the user can add new organisations def can_add_orgs? - perms.include? Perm.find_by(name: constant("user_role_types.add_organisations")) + perms.include? Perm.find_by(name: constant("roles.add_organisations")) end ## @@ -149,7 +159,7 @@ # # @return [Boolean] true if the user can change their organisation affiliations def can_change_org? - perms.include? Perm.find_by(name: constant("user_role_types.change_org_affiliation")) + perms.include? Perm.find_by(name: constant("roles.change_org_affiliation")) end ## @@ -157,7 +167,7 @@ # # @return [Boolean] true if the user can grant their permissions to others def can_grant_permissions? - perms.include? Perm.find_by(name: constant("user_role_types.grant_permissions")) + perms.include? Perm.find_by(name: constant("roles.grant_permissions")) end ## @@ -165,7 +175,7 @@ # # @return [Boolean] true if the user can modify organisation templates def can_modify_templates? - perms.include? Perm.find_by(name: constant("user_role_types.modify_templates")) + perms.include? Perm.find_by(name: constant("roles.modify_templates")) end ## @@ -173,7 +183,7 @@ # # @return [Boolean] true if the user can modify organistion guidance def can_modify_guidance? - perms.include? Perm.find_by(name: constant("user_role_types.modify_guidance")) + perms.include? Perm.find_by(name: constant("roles.modify_guidance")) end ## @@ -181,7 +191,7 @@ # # @return [Boolean] true if the user can use the api def can_use_api? - perms.include? Perm.find_by(name: constant("user_role_types.use_api")) + perms.include? Perm.find_by(name: constant("roles.use_api")) end ## @@ -189,15 +199,7 @@ # # @return [Boolean] true if the user can modify the org's details def can_modify_org_details? - perms.include? Perm.find_by(name: constant("user_role_types.change_org_details")) - end - - ## - # checks if the user can grant the api to organisations - # - # @return [Boolean] true if the user can grant api permissions to organisations - def can_grant_api_to_orgs? - perms.include? Perm.find_by(name: constant('user_role_types.grant_api_to_orgs')) + perms.include? Perm.find_by(name: constant("roles.change_org_details")) end @@ -206,18 +208,20 @@ # # @return [Boolean] true if the user can grant api permissions to organisations def can_grant_api_to_orgs? - perms.include? Perm.find_by(name: constant('user_role_types.grant_api_to_orgs')) + perms.include? Perm.find_by(name: constant('roles.grant_api_to_orgs')) end ## # checks what type the user's organisation is # # @return [String] the organisation type +=begin def org_type org_type = org.organisation_type return org_type end - +=end + ## # removes the api_token from the user # modifies the user model @@ -257,9 +261,11 @@ end end +# TODO: Remove this, its never called. # this generates a reset password link for a given user # which can then be sent to them with the appropriate host # prepended. +=begin def reset_password_link raw, enc = Devise.token_generator.generate(self.class, :reset_password_token) self.reset_password_token = enc @@ -268,5 +274,6 @@ edit_user_password_path + '?reset_password_token=' + raw end - +=end + end diff --git a/app/models/users_perm.rb b/app/models/users_perm.rb deleted file mode 100644 index 38a6cd3..0000000 --- a/app/models/users_perm.rb +++ /dev/null @@ -1,4 +0,0 @@ -class UsersPerm < ActiveRecord::Base - belongs_to :user - belongs_to :perm -end \ No newline at end of file diff --git a/app/policies/guidance_policy.rb b/app/policies/guidance_policy.rb index 4aa8245..725326c 100644 --- a/app/policies/guidance_policy.rb +++ b/app/policies/guidance_policy.rb @@ -53,7 +53,7 @@ class Scope < Scope def resolve - scope = Guidance.includes(:guidance_group, :question, :themes).by_organisation(user.org_id) + scope = Guidance.includes(:guidance_group, :themes).by_org(user.org_id) end end end \ No newline at end of file diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb index 49b9766..8ffe95a 100644 --- a/app/policies/note_policy.rb +++ b/app/policies/note_policy.rb @@ -2,22 +2,22 @@ attr_reader :user attr_reader :note - def initialize(user, comment) + def initialize(user, note) raise Pundit::NotAuthorizedError, "must be logged in" unless user @user = user @note = note end def create? - Plan.find(@note.plan_id).readable_by(@user.id) + @note.answer.plan.readable_by?(@user.id) end def update? - Plan.find(@note.plan_id).readable_by(@user.id) + Plan.find(@note.plan_id).readable_by?(@user.id) end def archive? - Plan.find(@note.plan_id).readable_by(@user.id) + Plan.find(@note.plan_id).readable_by?(@user.id) end -end \ No newline at end of file +end diff --git a/app/policies/plan_policy.rb b/app/policies/plan_policy.rb index 585e2ac..cb43258 100644 --- a/app/policies/plan_policy.rb +++ b/app/policies/plan_policy.rb @@ -7,49 +7,61 @@ @user = user @plan = plan end + + def show? + @plan.readable_by?(@user.id) + end def edit? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) + end + + def update_guidance_choices? + @plan.editable_by?(@user.id) + end + + def share? + @plan.readable_by?(@user.id) end def export? - @plan.readable_by(@user.id) + @plan.readable_by?(@user.id) end def update? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) end def status? - @plan.readable_by(@user.id) + @plan.readable_by?(@user.id) end def section_answers? - @plan.readable_by(@user.id) + @plan.readable_by?(@user.id) end def locked? - @plan.readable_by(@user.id) + @plan.readable_by?(@user.id) end def delete_recent_locks? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) end def unlock_all_sections? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) end def lock_section? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) end def unlock_section? - @plan.editable_by(@user.id) + @plan.editable_by?(@user.id) end def answer? - @plan.readable_by(@user.id) + @plan.readable_by?(@user.id) end -end \ No newline at end of file +end diff --git a/app/policies/template_policy.rb b/app/policies/template_policy.rb index 3524c40..1fd64f5 100644 --- a/app/policies/template_policy.rb +++ b/app/policies/template_policy.rb @@ -31,6 +31,10 @@ user.can_modify_templates? && (template.org_id == user.org_id) end + def admin_template_history? + user.can_modify_templates? && (template.org_id == user.org_id) + end + def admin_phase? user.can_modify_templates? && (template.org_id == user.org_id) end diff --git a/app/validators/answer_for_correct_template_validator.rb b/app/validators/answer_for_correct_template_validator.rb new file mode 100644 index 0000000..8ec15ee --- /dev/null +++ b/app/validators/answer_for_correct_template_validator.rb @@ -0,0 +1,10 @@ +class AnswerForCorrectTemplateValidator < ActiveModel::Validator + def validate(record) + # Make sure that the question and plan belong to the same template! + unless record.plan.nil? || record.question.nil? + unless record.plan.template == record.question.section.phase.template + record.errors[:question] << I18n.t('helpers.answer.question_must_belong_to_correct_template') + end + end + end +end \ No newline at end of file diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb index a3cc4ed..d47e7c1 100644 --- a/app/validators/email_validator.rb +++ b/app/validators/email_validator.rb @@ -1,7 +1,7 @@ class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i - #record.errors[attribute] << (options[:message] || "is not a valid email address") + record.errors[attribute] << (options[:message] || "is not a valid email address") end end end \ No newline at end of file diff --git a/app/views/admin/dmptemplates/settings.html.erb b/app/views/admin/dmptemplates/settings.html.erb index 171d5bc..c955083 100644 --- a/app/views/admin/dmptemplates/settings.html.erb +++ b/app/views/admin/dmptemplates/settings.html.erb @@ -4,15 +4,15 @@ <%= form_for(@settings, url: update_settings_admin_dmptemplate_path(@template), method: :put, as: 'settings[export][formatting]', html: { class: 'formtastic' }) do |f| %>
- <%= t("admin.formatting")%> + <%= _('Formatting') %>
  1. - <%= f.label(:font_face, t('helpers.settings.plans.font_face')) %> + <%= f.label(:font_face, _('Face')) %> <%= f.select(:font_face, options_for_select(Settings::Dmptemplate::VALID_FONT_FACES, @settings.formatting[:font_face]), as: 'formatting[font_face]') %> <%= f.select(:font_size, options_for_select((1..36).to_a, @settings.formatting[:font_size])) %>pt
  2. - <%= f.label(t('helpers.settings.plans.margin')) %> + <%= f.label(_('Margin')) %> <% ["top", "bottom", "left", "right"].each do |pos| %> <%= t("helpers.settings.plans.margins.#{pos}") -%> <%= select_tag("settings[export][formatting][margin][#{pos}]", options_for_select((0..100).to_a, @settings.formatting[:margin][pos])) %> @@ -22,17 +22,17 @@
- <%= t("admin.max_pages")%> + <%= _('Max Pages')%>
  1. - <%= label_tag('settings_export_max_pages', t('helpers.settings.plans.max_pages')) %> + <%= label_tag('settings_export_max_pages', _('Maximum number of pages')) %> <%= select_tag('settings[export][max_pages]', options_for_select((1..10).to_a, @settings.max_pages)) %>
- <%= submit_tag(t('helpers.save'), class: 'btn btn-primary') %> - <%= submit_tag(t('helpers.settings.plans.reset'), class: 'btn btn-primary') %> + <%= submit_tag(_('Save'), class: 'btn btn-primary') %> + <%= submit_tag(_('Reset'), class: 'btn btn-primary') %>
<% end %> diff --git a/app/views/api/v0/statistics/plans.json.jbuilder b/app/views/api/v0/statistics/plans.json.jbuilder index 1a05c9b..fe85ca8 100644 --- a/app/views/api/v0/statistics/plans.json.jbuilder +++ b/app/views/api/v0/statistics/plans.json.jbuilder @@ -1,25 +1,25 @@ json.prettify! -json.plans @org_projects.each do |project| - json.id project.id - json.grant_number project.grant_number - json.org_id project.organisation_id +json.plans @org_projects.each do |plan| + json.id plan.id + json.grant_number plan.grant_number + json.org_id plan.creator.org.id json.template do - json.title project.dmptemplate.title - json.id project.dmptemplate.id + json.title plan.template.title + json.id plan.template.id end json.project do - json.title project.title + json.title plan.title end json.funder do - json.name project.funder_name + json.name (plan.template.org.funder? ? plan.org.name : '') end json.principal_investigator do - json.name project.principal_investigator + json.name plan.principal_investigator end json.data_contact do - json.info project.data_contact + json.info plan.data_contact end - json.description project.description + json.description plan.description end \ No newline at end of file diff --git a/app/views/api/v0/statistics/plans_by_template.json.jbuilder b/app/views/api/v0/statistics/plans_by_template.json.jbuilder index 155bbba..d8d5485 100644 --- a/app/views/api/v0/statistics/plans_by_template.json.jbuilder +++ b/app/views/api/v0/statistics/plans_by_template.json.jbuilder @@ -1,14 +1,14 @@ json.prettify! templates = {} -@org_projects.each do |project| +@org_projects.each do |plan| # if hash exists - if templates[project.dmptemplate.title].blank? - templates[project.dmptemplate.title] = {} - templates[project.dmptemplate.title][:title] = project.dmptemplate.title - templates[project.dmptemplate.title][:id] = project.dmptemplate.id - templates[project.dmptemplate.title][:uses] = 1 + 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.id + templates[plan.template.title][:uses] = 1 else - templates[project.dmptemplate.title][:uses] += 1 + templates[plan.template.title][:uses] += 1 end end diff --git a/app/views/dmptemplates/_add_question.html.erb b/app/views/dmptemplates/_add_question.html.erb deleted file mode 100644 index b9bbfc8..0000000 --- a/app/views/dmptemplates/_add_question.html.erb +++ /dev/null @@ -1,183 +0,0 @@ - - -<% @new_question = Question.new %> -<% @new_question.number = section.questions.count + 1 %> - - -<%= form_for @new_question, :url => {:action => "admin_createquestion"}, :html => {:id => "new_question_#{section.id}"} do |f| %> -<%= f.hidden_field :section_id, :value => section.id %> -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<%= t("org_admin.questions.question_number_label")%><%= f.number_field :number, :in => 1..50, :class => "number_field has-tooltip", "data-toggle" => "tooltip", "title" => t("org_admin.questions.number_help_text") %> - -
-
<%= t("org_admin.questions.question_text_label")%><%= f.text_area :text, :rows => "5", :id => "new_question_text_#{section.id}" %> -
-
<%= t("org_admin.questions.answer_format_label")%><%= f.hidden_field :section_id, :value => section.id, :class => "section_id" %> -
-
- <%= f.select :question_format_id, - #options_from_collection_for_select(QuestionFormat.all.order("title"), :id, :title, QuestionFormat.find_by_title(t("helpers.text_area")).id), - # the above was the line but it doesn't work because in the DB - # the QuestionFormat title is in English (Text area) - # but the above uses the Fr translation and so gets a nil - options_from_collection_for_select(QuestionFormat.all.order("title"), :id, :title, QuestionFormat.find_by_title("Text area").id), - {}, :id => "new-select-format-#{section.id}"%> -
-
- <%= link_to( image_tag("help_button.png"), "#", :class => "question_format_popover", :rel => "popover", "data-html" => "true", "data-content" => t("org_admin.questions.question_format_help_text_html"))%> -
- -
-
- - -
- - -
- - -
- -
-
-
-
<%= t("org_admin.questions.suggested_or_example_answer_label")%>
- <% suggested_answer = @new_question.suggested_answers.build %> - <%= f.fields_for :suggested_answers, suggested_answer do |s|%> - <%= s.hidden_field :organisation_id, :value => current_user.organisation.id %> -
    -
  • <%= s.select :is_example, {t("org_admin.questions.example_answer_label") => true, t("org_admin.questions.suggested_answer_label") => false} %>
  • -
  • <%= s.text_area :text, :rows => 5 %>
  • -
- <%end%> -
-
- <%= link_to( image_tag("help_button.png"), "#", :class => "suggested_answer_popover", :rel => "popover", "data-html" => "true", "data-content" => t("org_admin.questions.suggested_answer_help_text_html"))%> -
-
-
- -
<%= t("org_admin.questions.guidance_label")%>
- <%= text_area_tag("new-question-guidance", "", class: "tinymce") %> -
-
- <%= link_to( image_tag("help_button.png"), "#", :class => "question_guidance_popover", :rel => "popover", "data-html" => "true", "data-content" => t("org_admin.questions.question_guidance_help_text_html"))%> -
-
-
-
<%= t("org_admin.questions.themes_label")%>
- <%= f.collection_select(:theme_ids, - Theme.all.order("title"), - :id, :title, {:prompt => false, :include_blank => t('helpers.none')}, {:multiple => true})%> -
-
- <%= link_to( image_tag("help_button.png"), "#", :class => "question_themes_popover", :rel => "popover", "data-html" => "true", "data-content" => t("org_admin.questions.question_themes_help_text_html"))%> -
- - -
-
- - -
- <%= hidden_field_tag :section_id, section.id, :class => "section_id" %> - <%= f.submit t("helpers.submit.save"), :class => "btn btn-primary new_question_save_button" %> - <%= hidden_field_tag :section_id, section.id, :class => "section_id_new" %> - <%= link_to t("helpers.submit.cancel"), '#', :class => "btn cancel cancel_add_new_question" %> -
-
- -<%end%> - diff --git a/app/views/dmptemplates/_admin_nav_tabs.html.erb b/app/views/dmptemplates/_admin_nav_tabs.html.erb deleted file mode 100644 index e2237ea..0000000 --- a/app/views/dmptemplates/_admin_nav_tabs.html.erb +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 4c03d50..40441b0 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -2,13 +2,10 @@
-

<%= raw t('welcome_title')%>

- <%= raw t('welcome_text', - application_name: Rails.configuration.branding[:application][:name], - organisation_name: Rails.configuration.branding[:organisation][:name])%> - +

<%= _('Welcome.')%>

+ <%= raw _('

%{application_name} has been jointly developed by the %{organisation_name} to help you write data management plans.

') % {:application_name => Rails.configuration.branding[:application][:name], :organisation_name => Rails.configuration.branding[:organisation][:name]} %>
-

<%= t('screencast_text', application_name: Rails.configuration.branding[:application][:name]) %>

+

<%= _('Screencast on how to use %{application_name}') % {:application_name => Rails.configuration.branding[:application][:name]} %>

@@ -30,7 +27,7 @@
- <%= t('helpers.sign_in')%> + <%= _('Sign in')%>
@@ -41,9 +38,9 @@
- <%= t('helpers.sign_up') %> + <%= _('Sign up') %>

- <%= t('helpers.sign_up_text', application_name: Rails.configuration.branding[:application][:name])%> + <%= _('New to %{application_name}? Sign up today.') % {:application_name => Rails.configuration.branding[:application][:name]} %>

diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb index f8728e7..897d824 100644 --- a/app/views/layouts/_footer.html.erb +++ b/app/views/layouts/_footer.html.erb @@ -3,9 +3,9 @@