diff --git a/app/assets/javascripts/questions.js.erb b/app/assets/javascripts/questions.js.erb index 92b7430e1..dae778188 100644 --- a/app/assets/javascripts/questions.js.erb +++ b/app/assets/javascripts/questions.js.erb @@ -175,6 +175,7 @@ function back(){ $.fancybox.update(); $('.block_content').css('width', 'auto'); } + function back_medias(){ go_to_medias(); } @@ -245,56 +246,106 @@ function edit_question(icon, replace_list){ return false; } + var questions_ids; + var ids; + if(replace_list != '.list_exams'){ - var questions_ids = $('.ckb_question:checked', $(icon).parents("div.list_questions")).map(function() { return this.value; }).get(); - var ids = questions_ids; + questions_ids = $('.ckb_question:checked', $(icon).parents("div.list_questions")).map(function() { return this.value; }).get(); + ids = questions_ids }else{ - var ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return this.value; }).get(); - var questions_ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('question-id'); }).get(); - var exam_id = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('exam-id'); }).get(); + ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return this.value; }).get(); + questions_ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('question-id'); }).get(); + var exam_id = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('exam-id'); }).get(); } if (!(!ids.length || ids.length > 1)) { - var base_verify_url = "<%=Rails.application.routes.url_helpers.verify_owners_question_path(':id')%>".replace(':id', questions_ids); + var base_verify_url = "<%=Rails.application.routes.url_helpers.verify_edit_permissions_question_path(':id')%>".replace(':id', questions_ids); $.get(exam_id ? (base_verify_url + `/${exam_id}`) : base_verify_url, function(data){ - var url_edit = $(icon).data('link-to-edit').replace(':id', ids); - $(icon).call_fancybox({ - href : url_edit, - open: true, - 'autoDimensions': false, - 'autoSize': false, - width: '700', - height: 'auto', - //maxHeight: '70%', - beforeClose: function() { clear_ckeditor(); } - }); - }).fail(function(data){ - var data = $.parseJSON(data.responseText); - if (!confirm(data.alert)) - return this; - - $.get("<%=Rails.application.routes.url_helpers.copy_verify_owners_question_path(':id')%>".replace(':id', questions_ids), function(data){ - var url_copy = $(icon).prev().data('link-to-copy').replace(':id', ids); - $(icon).prev().call_fancybox({ href : url_copy, open: true, maxHeight: '70%', 'autoDimensions': false, 'autoSize': false, width: '700', height: 'auto', }); - if(replace_list != ''){ - $.get($(replace_list).data("link-list"), function(data2){ - $(replace_list).replaceWith(data2); - flash_message(data.notice, 'notice'); - update_nice_checkbox(); - }); + if (data.action === 'only_points') { + if (!confirm(data.alert)) + return this; + } else if (data.action === 'can_fork_copy') { + if (confirm(data.alert)) { + data.action = 'only_points'; + } else { + if (confirm("<%=I18n.t('questions.error.fork_copy')%>")) { + data.action = 'fork_copy'; + } + return this; } - }).fail(function(data){ - var data = $.parseJSON(data.responseText); - if (typeof(data.alert) != "undefined") - flash_message(data.alert, 'alert'); - }); + } + + if (['copy_repository', 'copy', 'fork_copy'].includes(data.action)) { + + if (data.action !== 'fork_copy'){ + if (!confirm(data.alert)) + return this; + } + + var url_copy; + + if (data.action === 'copy_repository') { + url_copy = "<%=Rails.application.routes.url_helpers.copy_question_path(':id')%>".replace(':id', questions_ids); + } else if (data.action === 'fork_copy') { + url_copy = "<%=Rails.application.routes.url_helpers.fork_copy_exam_question_path(':id')%>".replace(':id', ids); + } else { + url_copy = $(icon).prev().data('link-to-copy').replace(':id', ids); + } + + $(icon).prev().call_fancybox({ + href : url_copy, + open: true, + maxHeight: '70%', + 'autoDimensions': false, + 'autoSize': false, + width: '700', + height: 'auto', + beforeClose: function() { clear_ckeditor(); } + }); + + if(replace_list !== ''){ + $.get($(replace_list).data("link-list"), function(data2){ + $(replace_list).replaceWith(data2); + flash_message(data.notice, 'notice'); + update_nice_checkbox(); + }); + } + } else { + + var url_edit = $(icon).data('link-to-edit').replace(':id', ids) + (data.action === 'only_points' ? "?only_points=true" : ""); + + if (data.notify) { + if (!confirm("<%=I18n.t('questions.questions.notify_editors_alert')%>")) { + return this; + } + url_edit += "?notify_responsibles=true"; + } + + $(icon).call_fancybox({ + href : url_edit, + open: true, + 'autoDimensions': false, + 'autoSize': false, + width: '700', + height: 'auto', + beforeClose: function() { clear_ckeditor(); } + }); + } + }).fail(function(data){ + var data = $.parseJSON(data.responseText); + if (typeof(data.alert) != "undefined") + flash_message(data.alert, 'alert'); }); + }else{ + flash_message("<%=I18n.t('questions.error.choose_one')%>", "alert"); + return false; } } function copy_question(icon, replace_list){ + if ($(icon).attr('disabled') == 'disabled'){ flash_message("<%=I18n.t('questions.error.choose_one')%>", "alert"); return false; @@ -410,6 +461,24 @@ function expand_or_compress_icon(icon){ } } +function toggle_alt_info(link){ + var parent = $(link).parent(); + parent.siblings().focus(); + var information = parent.siblings('.alt_info_text') + + if(information.hasClass('invisible')){ + information.removeClass('invisible') + parent.find('i.icon-arrow-up-triangle').removeClass('invisible'); + parent.find('i.icon-arrow-down-triangle').addClass('invisible'); + }else{ + information.addClass('invisible') + parent.find('i.icon-arrow-up-triangle').addClass('invisible'); + parent.find('i.icon-arrow-down-triangle').removeClass('invisible'); + } + + $(link).focus(); +} + function annul(icon){ if ($(icon).prop('disabled')) return false; diff --git a/app/controllers/exam_questions_controller.rb b/app/controllers/exam_questions_controller.rb index 1c160750c..04efbe9ef 100644 --- a/app/controllers/exam_questions_controller.rb +++ b/app/controllers/exam_questions_controller.rb @@ -24,7 +24,15 @@ def new def create @exam_question = ExamQuestion.new exam_question_params authorize! :create, Question, { on: at_ids = @exam_question.exam.allocation_tags.map(&:id) } - @exam_question.question.user_id = current_user.id + + if params[:forked_question_id].present? + @forked_question = ExamQuestion.find(params[:forked_question_id]) + @exam_question.question.user_id = @forked_question.question.user_id + @exam_question.question.updated_by_user_id = current_user.id unless current_user.id == @forked_question.question.user_id + @exam_question.question.status = true + else + @exam_question.question.user_id = current_user.id + end ActiveRecord::Base.transaction do if !params['question_texts']['text'].strip.blank? @@ -37,6 +45,12 @@ def create end if @exam_question.save + + if @forked_question.present? + log(@forked_question.exam, "question: #{@forked_question.question.id} [moved/deleted after copy] exam: #{@forked_question.exam.id}", LogAction::TYPE[:update]) rescue nil + @forked_question.destroy + end + if @exam_question.exam.questions.size > 1 render partial: 'question', locals: { question: @exam_question.question, exam_question: @exam_question, exam: @exam_question.exam, hide_columns: false, can_see_preview: true } else @@ -60,6 +74,7 @@ def create def edit @exam_question = ExamQuestion.find(params[:id]) + @only_points = params[:only_points].nil? ? false : true @question_text = QuestionText.find(@exam_question.question.question_text_id) unless @exam_question.question.question_text_id.blank? build_exam_question end @@ -89,6 +104,13 @@ def update end if @exam_question.update_attributes exam_question_params + if params[:notify_responsibles] == 'true' + message = t('questions.questions.notification', question_enunciation: @exam_question.question.enunciation) + @exam_question.question.exams.each do |exam| + exam.notify_responsibles(message) + end + end + if !params['question_texts_id'].blank? && params['question_texts']['media_question'].to_i == 0 if Question.where(:question_text_id => params['question_texts_id']).count == 0 QuestionText.find(params['question_texts_id']).destroy @@ -111,6 +133,28 @@ def update render_json_error(error, 'questions.error') end + def fork_copy + authorize! :copy, Question + + original_exam_question = ExamQuestion.find params[:id] + original_question = original_exam_question.question + original_question.can_copy? + + @new_question = original_question.dup + + copy_exam_question_associations(original_question, @new_question) + + @exam_question = ExamQuestion.new(original_exam_question.attributes.except('id', 'question_id', 'annulled').merge({ annulled: false })) + @exam_question.question = @new_question + @forked_question = original_exam_question + + render :new + rescue CanCan::AccessDenied + render text: t(:no_permission) + rescue => error + render text: t("exam_questions.errors.#{error}") + end + def remove_file_item authorize! :update, Question @@ -401,6 +445,24 @@ def log(object, message, type=LogAction::TYPE[:update]) end end + def copy_exam_question_associations(source_question, target_question) + source_question.question_items.each do |item| + target_question.question_items << item.dup + end + + source_question.question_images.each do |img| + target_question.question_images << img.dup + end + + source_question.question_labels.each do |label| + target_question.question_labels << label.dup + end + + source_question.question_audios.each do |audio| + target_question.question_audios << audio.dup + end + end + def build_exam_question if @exam_question.question.nil? @exam_question.build_question do |q| diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 2c9d64acc..0d2a82612 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -2,7 +2,7 @@ class QuestionsController < ApplicationController include SysLog::Actions - before_action :set_current_user, only: [:destroy, :change_status, :verify_owners, :copy, :index] + before_action :set_current_user, only: [:destroy, :change_status, :verify_owners, :verify_edit_permissions, :copy, :index] layout false, except: :index @@ -93,6 +93,13 @@ def update end if @question.update_attributes question_params + if params[:notify_responsibles] == 'true' && @question.previous_changes.present? + message = t('questions.questions.notification', question_enunciation: @question.enunciation) + @question.exams.each do |exam| + exam.notify_responsibles(message) + end + end + render partial: 'question', locals: { question: @question } else @errors = [] @@ -108,6 +115,29 @@ def update render_json_error(error, 'questions.error') end + def verify_edit_permissions + question = Question.find params[:id] + exam_id = params[:exam_id] + + edition_status = question.verify_edit_permission(current_user, exam_id) + + if [:edit_full, :edit_and_notify].include?(edition_status) + authorize! :update, Question + render json: { action: 'edit', notify: edition_status == :edit_and_notify }, status: :ok + else + repository_action = [:copy_blocked_exam, :copy_ownership].include?(edition_status) ? :copy : edition_status + render json: { action: repository_action, alert: I18n.t(get_edit_permissions_alert_key(edition_status), scope: [:questions, :error]) }, status: :ok + end + + rescue RuntimeError => error + if error.message == 'in_use' + error_message = I18n.translate!("#{error.message}", scope: [:questions, :error], :raise => true) rescue t('general_message', scope: [:questions, :error]) + render json: { success: false, alert: error_message }, status: :forbidden + end + rescue => error + render_json_error(error, 'questions.error') + end + def destroy authorize! :destroy, Question ActiveRecord::Base.transaction do @@ -135,10 +165,6 @@ def show def verify_owners question = Question.find params[:id] - if params[:update] - question.can_change?(params[:exam_id]) - authorize! :update, Question - end if params[:copy] question.can_copy? authorize! :copy, Question @@ -154,6 +180,7 @@ def verify_owners end def copy + authorize! :copy, Question question = Question.find params[:id] @@ -189,4 +216,21 @@ def log(object, message, type=LogAction::TYPE[:update]) LogAction.create(params_to_log.merge!(description: message, log_type: type)) end + def get_edit_permissions_alert_key(status) + case status + when :copy_blocked_exam + 'in_use_question' + when :copy_ownership + 'copy_ownership' + when :copy_repository + 'copy_repository' + when :only_points + 'only_points' + when :can_fork_copy + 'can_fork_copy' + else + 'in_use' + end + end + end diff --git a/app/models/allocation.rb b/app/models/allocation.rb index 18eceaf89..fccb22056 100644 --- a/app/models/allocation.rb +++ b/app/models/allocation.rb @@ -475,6 +475,19 @@ def calculate_grade_and_hours end end + def self.send_email_to_responsible_users(allocation_tag_ids ,subject, message_body) + responsibles_list = responsibles(allocation_tag_ids) + emails = responsibles_list.each do |responsible| + responsible.user.email + end + clean_emails = emails.compact.uniq + + if emails.any? + Job.send_mass_email(clean_emails, subject, message_body) + end + + end + private def verify_profile diff --git a/app/models/exam.rb b/app/models/exam.rb index 19e18ca1a..b902b748d 100644 --- a/app/models/exam.rb +++ b/app/models/exam.rb @@ -508,6 +508,29 @@ def after_immediate_result_release self.attempts = 1 end + # Return true if the exam is in draft mode, has not started, or started without ACU, and the user is the editor of the class where the exam is being used. + # Return false if exam is published and closed, or started with ACU, or started without ACU and user is not a class editor. + def can_questions_be_edited?(current_user, is_exam_question = false) + allocation_tags = academic_allocations.pluck(:allocation_tag_id) + is_editor = is_exam_question ? true : current_user.profiles_with_access_on('update', 'questions', allocation_tags, true).any? + + return false if ended? + return true unless status + return true unless started? + is_editor && !has_acu? + end + + def notify_responsibles(message) + allocation_tags = academic_allocations.pluck(:allocation_tag_id) + subject = I18n.t('exams.notification.subject', name: name, allocation_info: allocation_tag_info) + message_body = I18n.t('exams.notification.message_body', message: message, name: name, allocation_info: allocation_tag_info) + Allocation.send_email_to_responsible_users(allocation_tags, subject, message_body) + end + + def has_acu? + academic_allocation_users.any? + end + def current_time_db result = ActiveRecord::Base.connection.execute("SELECT current_time AS current_time") return Time.parse(result.to_a[0]['current_time']) diff --git a/app/models/exam_question.rb b/app/models/exam_question.rb index 63d9cde1a..6402a95c6 100644 --- a/app/models/exam_question.rb +++ b/app/models/exam_question.rb @@ -24,10 +24,10 @@ def recalculate_grades exam.recalculate_grades(nil,nil,nil,manually=true) end - def self.copy(exam_question_to_copy, user_id = nil) + def self.copy(exam_question_to_copy, user_id = nil, fork_copy = false) question = Question.copy(exam_question_to_copy.question, user_id) exam_question = ExamQuestion.create exam_question_to_copy.attributes.except('id', 'question_id', 'annulled').merge({ question_id: question.id, annulled: false }) - exam_question_to_copy.update_attributes annulled: true + exam_question_to_copy.update_attributes annulled: true unless fork_copy exam_question end @@ -98,7 +98,7 @@ def set_order end def can_reorder? - raise 'already_started' if exam.status && exam.on_going? + raise 'already_started' if exam.on_going? && exam.has_acu? end def can_change_annulled? @@ -119,20 +119,20 @@ def log_description end def can_save? - raise 'cant_change_after_published' if exam.status && (new_record? || question.status) + raise 'cant_change_after_published' if exam.has_acu? && (!question.privacy || question.owner? ) end def unpublish - exam.update_attributes status: false + exam.update_attributes status: false unless can_reorder? end - def each_item_score + def each_item_score item_score = case question.type_question when Question::UNIQUE score when Question::MULTIPLE (score/question_items.where(value: true).count).round(2) - else + else (score/question_items.count).round(2) end diff --git a/app/models/question.rb b/app/models/question.rb index 21b51c4fc..9a628f27d 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -245,13 +245,41 @@ def have_access? return User.current.profiles_with_access_on('show', 'questions', ats, true, false, true).any? end + # Return :edit_full if question can be fully edited (only in repository) + # Return :edit_and_notify if question can be edited but editors must be notified + # Return :copy_ownership and :copy_blocked_exam if question can only be copied (only in repository) + # Return :copy_repository if question can be copied only in repository and must not be changed in the exam + # Return :fork_copy if question can be copied but must be replaced in the exam + # Return :can_fork_copy if question can be copied and replaced in the exam or if only the points will be edited + # Return :only_points if only the points can be edited + # Raise an exception if the question cannot be edited + def verify_edit_permission(current_user, exam_id=nil) - def can_change?(exam_id = nil) - raise 'permission' unless owner? + if exam_id.nil? + has_blocking_exam = exams.any? do |exam| + next false if exam.nil? + !exam.can_questions_be_edited?(current_user) + end + + raise 'not_owner' if !owner? && privacy + + return :copy_blocked_exam if has_blocking_exam + return :copy_ownership unless owner? + exams.any? ? :edit_and_notify : :edit_full + else + has_access = owner? || !privacy + n_exams = exams.ids.length > 1 + can_be_edited = Exam.find(exam_id).can_questions_be_edited?(current_user, true) - if in_use?(exam_id) && status # if in use and already published - raise 'in_use_question' if exam_id.nil? #if on repository page - raise 'in_use_exam_question' #if on exams page + unless can_be_edited + return :copy_repository if has_access + raise 'not_owner' + end + + return :only_points unless has_access + return :fork_copy if n_exams + return :edit_and_notify if owner? + :can_fork_copy end end @@ -271,12 +299,8 @@ def owners?(s = false) end end - def in_use?(exam_id = nil) - if exam_id.nil? - exams.any? - else - (exams.ids.length > 1) - end + def in_use? + exams.exists? end def validate_images @@ -296,7 +320,7 @@ def can_import_or_export?(current_user, exam = nil) def can_copy? owners = owners? - raise 'private' unless (!privacy) || owners + raise 'private' unless owners || !privacy raise 'draft' if (!privacy && !status) && !owners end diff --git a/app/views/exam_questions/_question.html.haml b/app/views/exam_questions/_question.html.haml index 837a52b98..0cc787364 100644 --- a/app/views/exam_questions/_question.html.haml +++ b/app/views/exam_questions/_question.html.haml @@ -54,7 +54,7 @@ - unless labels.blank? .group_label= render 'questions/labels', labels: labels %td{ style: 'text-align:center;' }= question.type - %td{ style: 'text-align:center;' }= (question.try(:score) rescue exam_question.score) + %td{ style: 'text-align:center;' }= exam_question.score %td{ style: 'text-align:center;' }= l(question.updated_at, format: :normal) %td{ style: 'text-align:center;', class: hide_columns ? 'invisible' : '' }= link_to content_tag(:i, nil, class: 'icon-eye', :'data-tooltip' => t('questions.question.preview')), "#void", onclick: 'preview_question(this)', :'data-url' => question_path(question), class: 'preview_question', disabled: !(can_see_preview && question.can_see?(true)) %td{ style: 'text-align:center;' }= question.privacy? ? content_tag(:i, nil, class: 'icon-lock', :'data-tooltip' => t('questions.question.private')) : content_tag(:i, nil, class: 'icon-minus', :"data-tooltip" => t('questions.question.public')) diff --git a/app/views/exam_questions/_steps.html.haml b/app/views/exam_questions/_steps.html.haml index 566701d14..84c986e3e 100644 --- a/app/views/exam_questions/_steps.html.haml +++ b/app/views/exam_questions/_steps.html.haml @@ -1,5 +1,8 @@ .new_question = simple_form_for(@exam_question, html: { multipart: true, id: 'question_form' }) do |eq| + = hidden_field_tag :notify_responsibles, params[:notify_responsibles] + - if @forked_question.present? + = hidden_field_tag :forked_question_id, @forked_question.id %span.form_requirement= t(:required_fields) %h1#lightBoxDialogTitle= (eq.object.persisted? ? t('questions.form.steps.edit') : t('questions.form.steps.new')) = eq.input :exam_id, as: :hidden, input_html: { value: @exam_question.exam_id } @@ -8,18 +11,23 @@ #steps %ul %li.info.active - = t('questions.form.steps.info') - .dot.active#dot-info - %li.medias - = t('questions.form.steps.medias') - .dot#dot-medias - %li.items - = t('questions.form.steps.items') - .dot#dot-items + - if @only_points.present? + = t('questions.form.steps.score') + -else + = t('questions.form.steps.info') + .dot.active#dot-info + - unless @only_points.present? + %li.medias + = t('questions.form.steps.medias') + .dot#dot-medias + %li.items + = t('questions.form.steps.items') + .dot#dot-items = eq.simple_fields_for :question do |f| .step-info - = render partial: 'questions/form/info', locals: { f: f, eq: eq } - .step-medias.invisible - = render partial: 'questions/form/media', locals: { f: f, eq: nil } - .step-items.invisible - = render partial: 'questions/form/items', locals: { f: f, eq: eq } + = render partial: 'questions/form/info', locals: { f: f, eq: eq, only_points: @only_points } + - unless @only_points.present? + .step-medias.invisible + = render partial: 'questions/form/media', locals: { f: f, eq: nil } + .step-items.invisible + = render partial: 'questions/form/items', locals: { f: f, eq: eq } diff --git a/app/views/exam_questions/new.html.haml b/app/views/exam_questions/new.html.haml index d7742ca55..f967078ca 100644 --- a/app/views/exam_questions/new.html.haml +++ b/app/views/exam_questions/new.html.haml @@ -1,3 +1,3 @@ = javascript_include_tag 'questions' -= render partial: 'exam_questions/steps' += render partial: 'exam_questions/steps', locals: { only_points: false } diff --git a/app/views/questions/form/_image.html.haml b/app/views/questions/form/_image.html.haml index cd8833167..5b0b9e81e 100644 --- a/app/views/questions/form/_image.html.haml +++ b/app/views/questions/form/_image.html.haml @@ -39,7 +39,4 @@ $(btn).parent().find('input.file').click(); } - function toggle_alt_info(link){ - $(link).siblings().find('.alt_info_text').toggle('invisible'); - } diff --git a/app/views/questions/form/_info.html.haml b/app/views/questions/form/_info.html.haml index e8bd31a01..c334b47ba 100644 --- a/app/views/questions/form/_info.html.haml +++ b/app/views/questions/form/_info.html.haml @@ -1,8 +1,9 @@ = javascript_include_tag 'ckeditor/init' -.question_type - = f.label t('questions.new.privacy') - = f.select :privacy, ([ [t('questions.new.public'), false], [t('questions.new.private'), true] ]), include_blank: false +- unless only_points + .question_type + = f.label t('questions.new.privacy') + = f.select :privacy, ([ [t('questions.new.public'), false], [t('questions.new.private'), true] ]), include_blank: false .question_type = f.label t('questions.new.type_question') @@ -14,11 +15,13 @@ - unless eq.nil? = eq.input :score, input_html: { step: '0.5' } + -unless f.object.persisted? .correction_info .expand_correction =link_to('#void', onclick: 'toggle_text(this);' , onkeydown: 'click_on_keypress(event, this); ') do = t('questions.question.understanding_score_calculation') %i.icon-arrow-down-triangle{:'aria-hidden'=>'true'} + %i.icon-arrow-up-triangle.invisible{:'aria-hidden'=>'true'} .correction_text .invisible.true_or_false_calculation = raw(t('questions.types.true_or_false_calculation', score_item: t('questions.question.total_amount'))) @@ -27,24 +30,28 @@ .invisible.multiple_choice_new_calculation = raw(t('questions.types.multiple_choice_new_calculation', score_item: t('questions.question.total_amount_per_items'))) -.labels - = f.label t('questions.questions.labels'), class: 'label_labels' - %div#labels_container - = render partial: 'questions/form/label', locals: { f: f } - .input.string - = f.add_nested_fields_link :question_labels, t('.add_labels'), class: 'btn' +- unless only_points + .labels + = f.label t('questions.questions.labels'), class: 'label_labels' + %div#labels_container + = render partial: 'questions/form/label', locals: { f: f } + .input.string + = f.add_nested_fields_link :question_labels, t('.add_labels'), class: 'btn' -= f.input :enunciation, as: :ckeditor + = f.input :enunciation, as: :ckeditor -= label_tag 'question_enunciation', '', id: 'exam_question_question_enunciation' + = label_tag 'question_enunciation', '', id: 'exam_question_question_enunciation' -.ckb - = f.label t('questions.form.add_media'), id: 'add_media' - = link_to (image_tag "#{f.object.have_media? ? 'released' : 'rejected'}.png"), "#void", onclick: 'change(this)', onkeydown: 'click_on_keypress(event, this)', :'data-tooltip' => t('questions.form.add_media'), id: 'add_media_link' + .ckb + = f.label t('questions.form.add_media'), id: 'add_media' + = link_to (image_tag "#{f.object.have_media? ? 'released' : 'rejected'}.png"), "#void", onclick: 'change(this)', onkeydown: 'click_on_keypress(event, this)', :'data-tooltip' => t('questions.form.add_media'), id: 'add_media_link' .form-actions.right_buttons = button_tag t(:cancel), :type => 'button', :onclick => "jQuery.fancybox.close()", class: 'btn btn_default btn_lightbox', alt: t(:cancel) - = button_tag t('.continue'), :type => 'button', :onclick => "go_to_medias()", class: 'btn btn_main btn_lightbox', alt: t(:save), id: 'go_to' + - if only_points + = button_tag t(:save), :type => 'button', :onclick => "save_new_question(#{eq.nil? ? 0 : eq.object.exam_id}, #{f.object.type_question.nil? ? false : f.object.type_question})", class: 'btn btn_main', style: 'margin-bottom: 6px' + - else + = button_tag t('.continue'), :type => 'button', :onclick => "go_to_medias()", class: 'btn btn_main btn_lightbox', alt: t(:save), id: 'go_to' :javascript @@ -113,7 +120,7 @@ $("#question_texts_media_question").attr("checked","true"); } - $('#exam_question_question_attributes_type_question')[0].addEventListener("change", (event) => { + $('#exam_question_question_attributes_type_question, #question_type_question')[0].addEventListener("change", (event) => { if ($('.correction_text').children(':visible').length>0){ $('.expand_correction a').click(); } @@ -207,9 +214,12 @@ } function toggle_text(link){ - type = $('#exam_question_question_attributes_type_question').val(); + var type = $('#exam_question_question_attributes_type_question').length + ? $('#exam_question_question_attributes_type_question').val() + : $('#question_type_question').val(); - div = $('.correction_text').children(':visible'); + var div = $('.correction_text').children(':visible'); + var selected_div; if(type==0){ selected_div = $('.unique_choice_calculation.invisible'); @@ -221,12 +231,16 @@ } } - selected_div.toggleClass('invisible'); - div.toggleClass('invisible'); - - if(!!div.is(':visible')){ + if(selected_div.hasClass('invisible')){ focus_element(selected_div); + $(link).parent().find('i.icon-arrow-up-triangle').removeClass('invisible'); + $(link).parent().find('i.icon-arrow-down-triangle').addClass('invisible'); }else{ focus_element($(link).parent()); + $(link).parent().find('i.icon-arrow-up-triangle').addClass('invisible'); + $(link).parent().find('i.icon-arrow-down-triangle').removeClass('invisible'); } + + selected_div.toggleClass('invisible'); + div.toggleClass('invisible'); } diff --git a/app/views/questions/form/_item.html.haml b/app/views/questions/form/_item.html.haml index 3e32c46d6..cd7cc4f29 100644 --- a/app/views/questions/form/_item.html.haml +++ b/app/views/questions/form/_item.html.haml @@ -247,21 +247,3 @@ } }); } - - function toggle_alt_info(link){ - var parent = $(link).parent(); - parent.siblings().focus(); - var information = parent.siblings('.alt_info_text') - - if(information.hasClass('invisible')){ - information.removeClass('invisible') - parent.find('i.icon-arrow-up-triangle').removeClass('invisible'); - parent.find('i.icon-arrow-down-triangle').addClass('invisible'); - }else{ - information.addClass('invisible') - parent.find('i.icon-arrow-up-triangle').addClass('invisible'); - parent.find('i.icon-arrow-down-triangle').removeClass('invisible'); - } - - $(link).focus(); - } diff --git a/app/views/questions/form/_media.html.haml b/app/views/questions/form/_media.html.haml index 01888bc3a..e35d79ca3 100644 --- a/app/views/questions/form/_media.html.haml +++ b/app/views/questions/form/_media.html.haml @@ -60,22 +60,4 @@ $('#import').show(); }); - }) - - function toggle_alt_info(link){ - var parent = $(link).parent(); - parent.siblings().focus(); - var information = parent.siblings('.alt_info_text') - - if(information.hasClass('invisible')){ - information.removeClass('invisible') - parent.find('i.icon-arrow-up-triangle').removeClass('invisible'); - parent.find('i.icon-arrow-down-triangle').addClass('invisible'); - }else{ - information.addClass('invisible') - parent.find('i.icon-arrow-up-triangle').addClass('invisible'); - parent.find('i.icon-arrow-down-triangle').removeClass('invisible'); - } - - $(link).focus(); - } + }) \ No newline at end of file diff --git a/app/views/questions/form/_steps.html.haml b/app/views/questions/form/_steps.html.haml index 3b88444db..84dbbba9f 100644 --- a/app/views/questions/form/_steps.html.haml +++ b/app/views/questions/form/_steps.html.haml @@ -1,5 +1,6 @@ .new_question = simple_form_for(@question, html: { multipart: true, id: 'question_form' }) do |f| + = hidden_field_tag :notify_responsibles, params[:notify_responsibles] %span.form_requirement= t(:required_fields) %h1#lightBoxDialogTitle= (f.object.persisted? ? t('questions.form.steps.edit') : t('questions.form.steps.new')) .question_form.block_content @@ -16,7 +17,7 @@ = t('questions.form.steps.items') .dot#dot-items .step-info - = render partial: 'questions/form/info', locals: { f: f, eq: nil } + = render partial: 'questions/form/info', locals: { f: f, eq: nil, only_points: false } .step-medias.invisible = render partial: 'questions/form/media', locals: { f: f, eq: nil } .step-items.invisible diff --git a/config/locales/en_US.yml b/config/locales/en_US.yml index 90edf401c..f30d32753 100644 --- a/config/locales/en_US.yml +++ b/config/locales/en_US.yml @@ -4488,6 +4488,9 @@ en_US: link: "Attempt %{number} (Grade: %{grade})" warning: title: "Important: You can't create, import or remove questions while exam's published" + notification: + subject: "Change in the Exam %{name} (%{allocation_info}) " + message_body: "%{message}Verify if any changes are needed for Exam %{name} (%{allocation_info})." question: question: 'Question ' enunciation: 'Enunciation ' @@ -4552,6 +4555,8 @@ en_US: hide_alt: "Hide audio's text version" score: Scores associated_text: Associated text + notification: "There was a change in the question with the statement '%{question_enunciation}'. " + notify_editors_alert: "This question is in a draft exam, or a exam that has not been started or that has not been answered. When it is modified, the editors of the class when the exam will be applied will be notified. Do you wish to continue?" question: preview: Preview public: Public @@ -4596,6 +4601,7 @@ en_US: new: New question edit: Edit question medias: Images, audios or texts + score: Score items: answer: 'Answer*' add: Add more options @@ -4650,8 +4656,14 @@ en_US: labels: 'labels: ' error: in_use: "This question is being used on exams. We suggest you make a copy so you can modify it." - in_use_question: "This question is being used on a test. Would you like to duplicate it for editing?" + in_use_question: "This question is being used on a started test. Would you like to duplicate it for editing?" in_use_exam_question: "This question is being used on another exam. Would you like to duplicate it for editing?" + copy_ownership: "Only the author can edit the question. Do you want to make a copy?" + not_owner: "Only the author can edit the question. We suggest you make a copy so you can modify it." + copy_repository: "This question is in a test that has already begun or ended. Do you want to create a copy in the repository?" + only_points: "You are not authorized to edit the content of this question. Do you want to change the score of the selected question on the exam?" + can_fork_copy: "Do you want to change the score of the selected question on the exam?" + fork_copy: "Do you want to proceed to the editing screen?" privacy_in_use: "can't be defined as private, because is being used at exams. If you're the author and the question is in use only at your exams, we suggest that you remove it from the exam and change it at Repositories area. If the question is in use at others exams that are not yours, it's not possible to change it." privacy_permission: can't be defined as private. You don't have permission to execute this action. min_items: The question must have, at least, 3 items diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index a30bf1e8b..5985eefdd 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -4810,6 +4810,9 @@ pt_BR: link: "Tentativa %{number} (Nota: %{grade})" warning: title: 'Importante: Não é possível criar, importar ou remover questões em provas publicadas' + notification: + subject: "Alteração na Prova %{name} (%{allocation_info}) " + message_body: "%{message}Verifique se alterações na Prova %{name} (%{allocation_info}) são necessárias." score: 'Pontuação ' question: question: 'Questão ' @@ -4875,6 +4878,8 @@ pt_BR: hide_alt: Ocultar versão textual do áudio score: Pontuação associated_text: Texto associado + notification: "Houve uma alteração na questão com o enunciado '%{question_enunciation}'. " + notify_editors_alert: "Esta questão está em um rascunho de prova, ou em uma prova que ainda não foi iniciada ou respondida. Quando for modificada, os editores da disciplina em que a prova será aplicada serão notificados. Deseja continuar?" question: preview: Visualização public: Pública @@ -4919,6 +4924,7 @@ pt_BR: new: Nova questão edit: Editar questão medias: Imagens, áudios ou textos + score: Pontuação items: answer: 'Resposta*' add: Adicionar mais opções @@ -4972,8 +4978,14 @@ pt_BR: labels: 'palavras-chave: ' error: in_use: "Essa questão está sendo utilizada em provas. Sugerimos que faça uma cópia para que possa modificá-la." - in_use_question: "Essa questão está sendo utilizada em uma prova. Deseja duplicá-la para fazer a edição?" + in_use_question: "Essa questão está sendo utilizada em uma prova já iniciada. Deseja duplicá-la para fazer a edição?" in_use_exam_question: "Essa questão está sendo utilizada em outra prova. Deseja duplicá-la para fazer a edição?" + copy_ownership: "Somente o autor pode editar a questão. Deseja fazer uma cópia?" + not_owner: "Somente o autor pode editar a questão. Sugerimos que faça uma cópia para que possa modificá-la." + copy_repository: "Está questão está em uma prova já iniciada ou encerrada. Deseja criar uma cópia no repositório?" + only_points: "Você não tem autorização para editar o conteúdo desta questão. Deseja alterar a pontuação da questão selecionada na prova?" + can_fork_copy: "Deseja alterar somente a pontuação da questão selecionada na prova?" + fork_copy: "Deseja seguir para a tela de edição?" privacy_in_use: "não pode ser definida como privada, pois está sendo usada em provas. Se você é o autor e a questão está em uso apenas em sua prova, sugerimos que remova-a de sua prova e se dirija ao repositório para alterá-la. Caso a questão esteja em uso em outras provas não criadas por você, não é possível alterá-la" privacy_permission: não pode ser definida como privada. Você não tem permissão para realizar esta ação. min_items: A questão precisa ter, ao menos, 3 itens diff --git a/config/routes.rb b/config/routes.rb index 76f647f9f..e81095a59 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -695,7 +695,7 @@ member do put :change_status put :publish, action: :change_status, status: true - get 'verify_owners(/:exam_id)', as: :verify_owners, action: :verify_owners, update: true + get 'verify_edit_permissions(/:exam_id)', as: :verify_edit_permissions, action: :verify_edit_permissions get :copy_verify_owners, action: :verify_owners, copy: true get :show_verify_owners, action: :verify_owners, show: true get :copy @@ -709,6 +709,7 @@ get '/export/exam_questions/steps', to: 'exam_questions#export_steps', as: :export_steps put :publish get :copy + get :fork_copy put :remove_image_item, to: 'exam_questions#remove_file_item', type: 'image' put :remove_audio_item, to: 'exam_questions#remove_file_item', type: 'audio' end