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