Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
canvas
/
app
/
models
/
quizzes
/
Filename :
quiz_question_builder.rb
back
Copy
# frozen_string_literal: true # # Copyright (C) 2014 - present Instructure, Inc. # # This file is part of Canvas. # # Canvas is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, version 3 of the License. # # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along # with this program. If not, see <http://www.gnu.org/licenses/>. class Quizzes::QuizQuestionBuilder QUIZ_GROUP_ENTRY = "quiz_group" DEFAULT_OPTIONS = { shuffle_answers: false }.freeze class << self def t(*args) ::ActiveRecord::Base.t(*args) end end def initialize(options = {}) self.options = DEFAULT_OPTIONS.merge(options) end attr_accessor :options # Shuffle questions in quiz groups. Shuffle answers & matches in individual # questions. # # @note # Quiz groups that are tied to a question bank are not touched here. That # happens when we prepare questions for a single user/submission in # #build_submission_questions(). def shuffle_quiz_data!(questions) questions.each do |val| # A quiz group linked to a question bank: if val[:entry_type] == QUIZ_GROUP_ENTRY && val[:assessment_question_bank_id] # It points to a question bank, question/answer/match shuffling happens # when a submission is generated. See #build_submission_questions() # A normal quiz group with questions: elsif val[:entry_type] == QUIZ_GROUP_ENTRY val[:questions].shuffle! val[:questions].each do |question| if question[:answers] question[:answers] = shuffle_answers(question) question[:matches] = shuffle_matches(question) if question[:matches] end end # A normal question: elsif val[:answers] val[:answers] = shuffle_answers(val) val[:matches] = shuffle_matches(val) if val[:matches] end end end # Build the question data for a specific submission. This is what the user # will end up taking in their quiz. # # Based on the type of entries the quiz has in its quiz_data, each # submission's quiz_data construct may be unique since questions may be drawn # randomly out of pre-defined pools. # # @param [Integer] quiz_id # @param [Array<Hash>] quiz_data # The pre-shuffled quiz question entries. This is what's stored in # Quiz#stored_questions. # # @return [Array<Hash>] # What you'd store in QuizSubmission#quiz_data. def build_submission_questions(quiz_id, quiz_data) @submission_question_index = 0 @picked = { aq: [], qq: [] } # initially, exclude all the questions defined locally in the quiz from bank # selections: mark_picked(quiz_data.reject { |d| d[:entry_type] == QUIZ_GROUP_ENTRY }) quiz_data.reduce([]) do |submission_questions, descriptor| # pulling from question bank questions = if descriptor[:entry_type] == QUIZ_GROUP_ENTRY && descriptor[:assessment_question_bank_id] if (bank = ::AssessmentQuestionBank.where(id: descriptor[:assessment_question_bank_id]).first) pool = BankPool.new(bank, @picked, &method(:mark_picked)) pool.draw(quiz_id, descriptor[:id], descriptor[:pick_count]).each do |question| question[:points_possible] = descriptor[:question_points] question[:published_at] = descriptor[:published_at] # since these questions were not resolved when the quiz's questions # were generated (because they're different for each user/submission), # so we need to decorate them like we did the group questions back in # #shuffle_quiz_data!() if question[:answers] question[:answers] = shuffle_answers(question) question[:matches] = shuffle_matches(question) if question[:matches] end end end # pulling from questions defined directly in a group elsif descriptor[:entry_type] == QUIZ_GROUP_ENTRY pool = GroupPool.new(descriptor[:questions], @picked, &method(:mark_picked)) pool.draw(quiz_id, descriptor[:id], descriptor[:pick_count]).each do |question| question[:points_possible] = descriptor[:question_points] end # just a question else [descriptor] end questions.each do |question| decorate_question_for_submission(question) end submission_questions.concat(questions) end end def self.decorate_question_for_submission(q, position) question_name = t( "#quizzes.quiz.question_name_counter", "Question %{question_number}", { question_number: position } ) q[:position] = position case q[:question_type] when ::Quizzes::QuizQuestion::Q_TEXT_ONLY question_name = t("#quizzes.quiz.default_text_only_question_name", "Spacer") when ::Quizzes::QuizQuestion::Q_FILL_IN_MULTIPLE_BLANKS text = q[:question_text] variables = q[:answers].pluck(:blank_id).uniq variables.each do |variable| variable_id = ::AssessmentQuestion.variable_id(variable) re = Regexp.new("\\[#{variable}\\]") text = text.sub re, <<~HTML <input class='question_input' type='text' autocomplete='off' style='width: 120px;' name='question_#{q[:id]}_#{variable_id}' value='{{question_#{q[:id]}_#{variable_id}}}' /> HTML end q[:original_question_text] = q[:question_text] q[:question_text] = text when ::Quizzes::QuizQuestion::Q_MULTIPLE_DROPDOWNS text = q[:question_text] variables = q[:answers].pluck(:blank_id).uniq variables.each do |variable| variable_id = ::AssessmentQuestion.variable_id(variable) variable_answers = q[:answers].select { |a| a[:blank_id] == variable } options = variable_answers.map do |a| answer_text = Utf8Cleaner.strip_invalid_utf8(a[:text]) "<option value='#{a[:id]}'>#{CGI.escapeHTML(answer_text)}</option>" end select = <<~HTML <select class='question_input' name='question_#{q[:id]}_#{variable_id}'> <option value=''> #{ERB::Util.h(t("#quizzes.quiz.default_question_input", "[ Select ]"))} </option> #{options} </select> HTML re = Regexp.new("\\[#{variable}\\]") text = text.sub(re, select) end # variable loop q[:original_question_text] = q[:question_text] q[:question_text] = text when ::Quizzes::QuizQuestion::Q_CALCULATED # on equation questions, pick one of the formulas, plug it in # and you should be able to treat it like a numerical_answer # question for all intents and purposes text = q[:question_text] q[:answers] = [q[:answers].sample].compact if q[:answers].first q[:answers].first[:variables].each do |variable| re = Regexp.new("\\[#{variable[:name]}\\]") text = text.gsub(re, I18n.n(TextHelper.round_if_whole(variable[:value]).to_s)) end end q[:question_text] = text end q[:name] = q[:question_name] = question_name q end def shuffle_answers(question) if @options[:shuffle_answers] && shuffleable_question_type?(question[:question_type]) question[:answers].shuffle else question[:answers] end end def shuffle_matches(question) # question matches should always be shuffled, regardless of the # shuffle_answers option question[:matches].shuffle end protected # @property [Integer] submission_question_index # @private # # A counter used in generating question names for students based on the # position of the question within the quiz_data set. attr_reader :submission_question_index attr_reader :picked def decorate_question_for_submission(question) unless question[:question_type] == ::Quizzes::QuizQuestion::Q_TEXT_ONLY @submission_question_index += 1 end self.class.decorate_question_for_submission(question, @submission_question_index) end def shuffleable_question_type?(question_type) # TODO: constantize !%w[ true_false_question matching_question fill_in_multiple_blanks_question ].include?(question_type) end def mark_picked(questions) @picked[:aq].concat(questions.pluck(:assessment_question_id)).uniq! @picked[:qq].concat(questions.pluck(:id)) end end