Ejemplo n.º 1
0
// Update time now, in case the server is running really slowly.
$attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow, $quizobj->is_preview_user());
if (!($quizobj->get_quiz()->attemptonlast && $lastattempt)) {
    // Starting a normal, new, quiz attempt.
    // Fully load all the questions in this quiz.
    $quizobj->preload_questions();
    $quizobj->load_questions();
    // Add them all to the $quba.
    $idstoslots = array();
    $questionsinuse = array_keys($quizobj->get_questions());
    foreach ($quizobj->get_questions() as $i => $questiondata) {
        if ($questiondata->qtype != 'random') {
            if (!$quizobj->get_quiz()->shuffleanswers) {
                $questiondata->options->shuffleanswers = false;
            }
            $question = question_bank::make_question($questiondata);
        } else {
            $question = question_bank::get_qtype('random')->choose_other_question($questiondata, $questionsinuse, $quizobj->get_quiz()->shuffleanswers);
            if (is_null($question)) {
                throw new moodle_exception('notenoughrandomquestions', 'quiz', $quizobj->view_url(), $questiondata);
            }
        }
        $idstoslots[$i] = $quba->add_question($question, $questiondata->maxmark);
        $questionsinuse[] = $question->id;
    }
    // Start all the questions.
    if ($attempt->preview) {
        $variantoffset = rand(1, 100);
    } else {
        $variantoffset = $attemptnumber;
    }
Ejemplo n.º 2
0
 protected function initialise_question_instance($question, $questiondata)
 {
     parent::initialise_question_instance($question, $questiondata);
     $bits = preg_split('/\\{#(\\d+)\\}/', $question->questiontext, null, PREG_SPLIT_DELIM_CAPTURE);
     $question->textfragments[0] = array_shift($bits);
     $i = 1;
     while (!empty($bits)) {
         $question->places[$i] = array_shift($bits);
         $question->textfragments[$i] = array_shift($bits);
         $i += 1;
     }
     foreach ($questiondata->options->questions as $key => $subqdata) {
         $subqdata->contextid = $questiondata->contextid;
         $question->subquestions[$key] = question_bank::make_question($subqdata);
         $question->subquestions[$key]->maxmark = $subqdata->defaultmark;
         if (isset($subqdata->options->layout)) {
             $question->subquestions[$key]->layout = $subqdata->options->layout;
         }
     }
 }
Ejemplo n.º 3
0
 public function test_render_missing()
 {
     $questiondata = $this->get_unknown_questiondata();
     $q = question_bank::make_question($questiondata);
     $qa = new testable_question_attempt($q, 0);
     $step = new question_attempt_step(array('answer' => 'frog'));
     $step->set_state(question_state::$todo);
     $qa->add_step($step);
     $qa->set_behaviour(new qbehaviour_deferredfeedback($qa, 'deferredfeedback'));
     $output = $qa->render(new question_display_options(), '1');
     $this->assertRegExp('/' . preg_quote($qa->get_question()->questiontext, '/') . '/', $output);
     $this->assertRegExp('/' . preg_quote(get_string('missingqtypewarning', 'qtype_missingtype'), '/') . '/', $output);
     $this->assert(new question_contains_tag_with_attribute('div', 'class', 'warning missingqtypewarning'), $output);
 }
 /**
  * Starts an attempt and returns the object representing it.
  *
  * @param int $quizid
  * @param int $userid
  * @throws moodle_exception
  * @return \stdClass
  */
 public function start_quiz_attempt($quizid, $userid)
 {
     global $DB;
     $quizobj = quiz::create($quizid, $userid);
     $timenow = time();
     $attempt = quiz_create_attempt($quizobj->get_quiz(), 1, false, $timenow, false);
     // Taken from /mod/quiz/startattempt.php.
     $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
     $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
     // Starting a normal, new, quiz attempt.
     // Fully load all the questions in this quiz.
     $quizobj->preload_questions();
     $quizobj->load_questions();
     // Add them all to the $quba.
     $idstoslots = array();
     $questionsinuse = array_keys($quizobj->get_questions());
     foreach ($quizobj->get_questions() as $i => $questiondata) {
         if ($questiondata->qtype != 'random') {
             if (!$quizobj->get_quiz()->shuffleanswers) {
                 $questiondata->options->shuffleanswers = false;
             }
             $question = question_bank::make_question($questiondata);
         } else {
             $question = question_bank::get_qtype('random')->choose_other_question($questiondata, $questionsinuse, $quizobj->get_quiz()->shuffleanswers);
             if (is_null($question)) {
                 throw new moodle_exception('notenoughrandomquestions', 'quiz', $quizobj->view_url(), $questiondata);
             }
         }
         $idstoslots[$i] = $quba->add_question($question, $questiondata->maxmark);
         $questionsinuse[] = $question->id;
     }
     // Start all the questions.
     if ($attempt->preview) {
         $variantoffset = rand(1, 100);
     } else {
         $variantoffset = 1;
     }
     $quba->start_all_questions(new question_variant_pseudorandom_no_repeats_strategy($variantoffset), $timenow);
     // Update attempt layout.
     $newlayout = array();
     foreach (explode(',', $attempt->layout) as $qid) {
         if ($qid != 0) {
             $newlayout[] = $idstoslots[$qid];
         } else {
             $newlayout[] = 0;
         }
     }
     $attempt->layout = implode(',', $newlayout);
     question_engine::save_questions_usage_by_activity($quba);
     $attempt->uniqueid = $quba->get_id();
     $attempt->id = $DB->insert_record('quiz_attempts', $attempt);
     return $attempt;
 }
Ejemplo n.º 5
0
/**
 * Start a normal, new, quiz attempt.
 *
 * @param quiz      $quizobj            the quiz object to start an attempt for.
 * @param question_usage_by_activity $quba
 * @param object    $attempt
 * @param integer   $attemptnumber      starting from 1
 * @param integer   $timenow            the attempt start time
 * @param array     $questionids        slot number => question id. Used for random questions, to force the choice
 *                                        of a particular actual question. Intended for testing purposes only.
 * @param array     $forcedvariantsbyslot slot number => variant. Used for questions with variants,
 *                                          to force the choice of a particular variant. Intended for testing
 *                                          purposes only.
 * @throws moodle_exception
 * @return object   modified attempt object
 */
function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow,
                                $questionids = array(), $forcedvariantsbyslot = array()) {
    // Fully load all the questions in this quiz.
    $quizobj->preload_questions();
    $quizobj->load_questions();

    // Add them all to the $quba.
    $idstoslots = array();
    $questionsinuse = array_keys($quizobj->get_questions());
    foreach ($quizobj->get_questions() as $i => $questiondata) {
        if ($questiondata->qtype != 'random') {
            if (!$quizobj->get_quiz()->shuffleanswers) {
                $questiondata->options->shuffleanswers = false;
            }
            $question = question_bank::make_question($questiondata);

        } else {
            if (!isset($questionids[$quba->next_slot_number()])) {
                $forcequestionid = null;
            } else {
                $forcequestionid = $questionids[$quba->next_slot_number()];
            }

            $question = question_bank::get_qtype('random')->choose_other_question(
                $questiondata, $questionsinuse, $quizobj->get_quiz()->shuffleanswers, $forcequestionid);
            if (is_null($question)) {
                throw new moodle_exception('notenoughrandomquestions', 'quiz',
                                           $quizobj->view_url(), $questiondata);
            }
        }

        $idstoslots[$i] = $quba->add_question($question, $questiondata->maxmark);
        $questionsinuse[] = $question->id;
    }

    // Start all the questions.
    if ($attempt->preview) {
        $variantoffset = rand(1, 100);
    } else {
        $variantoffset = $attemptnumber;
    }
    $variantstrategy = new question_variant_pseudorandom_no_repeats_strategy(
            $variantoffset, $attempt->userid, $quizobj->get_quizid());

    if (!empty($forcedvariantsbyslot)) {
        $forcedvariantsbyseed = question_variant_forced_choices_selection_strategy::prepare_forced_choices_array(
            $forcedvariantsbyslot, $quba);
        $variantstrategy = new question_variant_forced_choices_selection_strategy(
            $forcedvariantsbyseed, $variantstrategy);
    }

    $quba->start_all_questions($variantstrategy, $timenow);

    // Update attempt layout.
    $newlayout = array();
    foreach (explode(',', $attempt->layout) as $qid) {
        if ($qid != 0) {
            $newlayout[] = $idstoslots[$qid];
        } else {
            $newlayout[] = 0;
        }
    }
    $attempt->layout = implode(',', $newlayout);
    return $attempt;
}
Ejemplo n.º 6
0
/**
 * Start a normal, new, quiz attempt.
 *
 * @param quiz      $quizobj            the quiz object to start an attempt for.
 * @param question_usage_by_activity $quba
 * @param object    $attempt
 * @param integer   $attemptnumber      starting from 1
 * @param integer   $timenow            the attempt start time
 * @param array     $questionids        slot number => question id. Used for random questions, to force the choice
 *                                        of a particular actual question. Intended for testing purposes only.
 * @param array     $forcedvariantsbyslot slot number => variant. Used for questions with variants,
 *                                          to force the choice of a particular variant. Intended for testing
 *                                          purposes only.
 * @throws moodle_exception
 * @return object   modified attempt object
 */
function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow, $questionids = array(), $forcedvariantsbyslot = array())
{
    // Usages for this user's previous quiz attempts.
    $qubaids = new \mod_quiz\question\qubaids_for_users_attempts($quizobj->get_quizid(), $attempt->userid);
    // Fully load all the questions in this quiz.
    $quizobj->preload_questions();
    $quizobj->load_questions();
    // First load all the non-random questions.
    $randomfound = false;
    $slot = 0;
    $questions = array();
    $maxmark = array();
    $page = array();
    foreach ($quizobj->get_questions() as $questiondata) {
        $slot += 1;
        $maxmark[$slot] = $questiondata->maxmark;
        $page[$slot] = $questiondata->page;
        if ($questiondata->qtype == 'random') {
            $randomfound = true;
            continue;
        }
        if (!$quizobj->get_quiz()->shuffleanswers) {
            $questiondata->options->shuffleanswers = false;
        }
        $questions[$slot] = question_bank::make_question($questiondata);
    }
    // Then find a question to go in place of each random question.
    if ($randomfound) {
        $slot = 0;
        $usedquestionids = array();
        foreach ($questions as $question) {
            if (isset($usedquestions[$question->id])) {
                $usedquestionids[$question->id] += 1;
            } else {
                $usedquestionids[$question->id] = 1;
            }
        }
        $randomloader = new \core_question\bank\random_question_loader($qubaids, $usedquestionids);
        foreach ($quizobj->get_questions() as $questiondata) {
            $slot += 1;
            if ($questiondata->qtype != 'random') {
                continue;
            }
            // Deal with fixed random choices for testing.
            if (isset($questionids[$quba->next_slot_number()])) {
                if ($randomloader->is_question_available($questiondata->category, (bool) $questiondata->questiontext, $questionids[$quba->next_slot_number()])) {
                    $questions[$slot] = question_bank::load_question($questionids[$quba->next_slot_number()], $quizobj->get_quiz()->shuffleanswers);
                    continue;
                } else {
                    throw new coding_exception('Forced question id not available.');
                }
            }
            // Normal case, pick one at random.
            $questionid = $randomloader->get_next_question_id($questiondata->category, (bool) $questiondata->questiontext);
            if ($questionid === null) {
                throw new moodle_exception('notenoughrandomquestions', 'quiz', $quizobj->view_url(), $questiondata);
            }
            $questions[$slot] = question_bank::load_question($questionid, $quizobj->get_quiz()->shuffleanswers);
        }
    }
    // Finally add them all to the usage.
    ksort($questions);
    foreach ($questions as $slot => $question) {
        $newslot = $quba->add_question($question, $maxmark[$slot]);
        if ($newslot != $slot) {
            throw new coding_exception('Slot numbers have got confused.');
        }
    }
    // Start all the questions.
    $variantstrategy = new core_question\engine\variants\least_used_strategy($quba, $qubaids);
    if (!empty($forcedvariantsbyslot)) {
        $forcedvariantsbyseed = question_variant_forced_choices_selection_strategy::prepare_forced_choices_array($forcedvariantsbyslot, $quba);
        $variantstrategy = new question_variant_forced_choices_selection_strategy($forcedvariantsbyseed, $variantstrategy);
    }
    $quba->start_all_questions($variantstrategy, $timenow);
    // Work out the attempt layout.
    $sections = $quizobj->get_sections();
    foreach ($sections as $i => $section) {
        if (isset($sections[$i + 1])) {
            $sections[$i]->lastslot = $sections[$i + 1]->firstslot - 1;
        } else {
            $sections[$i]->lastslot = count($questions);
        }
    }
    $layout = array();
    foreach ($sections as $section) {
        if ($section->shufflequestions) {
            $questionsinthissection = array();
            for ($slot = $section->firstslot; $slot <= $section->lastslot; $slot += 1) {
                $questionsinthissection[] = $slot;
            }
            shuffle($questionsinthissection);
            $questionsonthispage = 0;
            foreach ($questionsinthissection as $slot) {
                if ($questionsonthispage && $questionsonthispage == $quizobj->get_quiz()->questionsperpage) {
                    $layout[] = 0;
                    $questionsonthispage = 0;
                }
                $layout[] = $slot;
                $questionsonthispage += 1;
            }
        } else {
            $currentpage = $page[$section->firstslot];
            for ($slot = $section->firstslot; $slot <= $section->lastslot; $slot += 1) {
                if ($currentpage !== null && $page[$slot] != $currentpage) {
                    $layout[] = 0;
                }
                $layout[] = $slot;
                $currentpage = $page[$slot];
            }
        }
        // Each section ends with a page break.
        $layout[] = 0;
    }
    $attempt->layout = implode(',', $layout);
    return $attempt;
}
 /**
  * Creates a single printable copy of the given quiz.
  *
  * @return  The ID of the created printable question usage.
  *
  */
 protected function create_printable_copy($shuffle_mode = quiz_papercopy_shuffle_modes::MODE_SHUFFLE_IGNORE_PAGES, $fix_descriptions = false, $fix_first = false, $fix_last = false)
 {
     //get a reference to the current user
     global $USER;
     //create a new usage object, which will allow us to create a psuedoquiz in the same context as the online quiz
     $usage = question_engine::make_questions_usage_by_activity('mod_quiz', $this->context);
     //and set the grading mode to "deferred feedback", the standard for paper quizzes
     //this makes sense, since our paradigm is duriven by the idea that feedback is only offered once a paper quiz has been uploaded/graded
     $usage->set_preferred_behaviour('deferredfeedback');
     //get an array of questions in the current quiz
     $quiz_questions = $this->quizobj->get_questions();
     //randomize the question order, as requested
     $quiz_questions = self::shuffle_questions($quiz_questions, $this->quiz->questions, $shuffle_mode, $fix_descriptions, $fix_first, $fix_last);
     //for each question in our online quiz
     foreach ($quiz_questions as $slot => $qdata) {
         $question = question_bank::make_question($qdata);
         //add the new question instance to our new printable copy, keeping the maximum grade from the quiz
         //TODO: respect maximum marks
         $usage->add_question($question);
     }
     //initialize each of the questions
     $usage->start_all_questions();
     //save the usage to the database
     question_engine::save_questions_usage_by_activity($usage);
     //return the ID of the newly created questions usage
     return $usage->get_id();
 }
Ejemplo n.º 8
0
/**
 * Retrieves a template question usage for an offline group. Creates a new template if there is none.
 * While creating question usage it shuffles the group questions if shuffleanswers is created.
 *
 * @param object $offlinequiz
 * @param object $group
 * @param object $context
 * @return question_usage_by_activity
 */
function offlinequiz_get_group_template_usage($offlinequiz, $group, $context)
{
    global $CFG, $DB;
    if (!empty($group->templateusageid) && $group->templateusageid > 0) {
        $templateusage = question_engine::load_questions_usage_by_activity($group->templateusageid);
    } else {
        $questionids = offlinequiz_get_group_question_ids($offlinequiz, $group->id);
        if ($offlinequiz->shufflequestions) {
            $offlinequiz->groupid = $group->id;
            $questionids = offlinequiz_shuffle_questions($questionids);
        }
        // We have to use our own class s.t. we can use the clone function to create results.
        $templateusage = offlinequiz_make_questions_usage_by_activity('mod_offlinequiz', $context);
        $templateusage->set_preferred_behaviour('immediatefeedback');
        if (!$questionids) {
            print_error(get_string('noquestionsfound', 'offlinequiz'), 'view.php?q=' . $offlinequiz->id);
        }
        // Gets database raw data for the questions.
        $questiondata = question_load_questions($questionids);
        // Get the question instances for initial markmarks.
        $sql = "SELECT questionid, maxmark\n                  FROM {offlinequiz_group_questions}\n                 WHERE offlinequizid = :offlinequizid\n                   AND offlinegroupid = :offlinegroupid ";
        $groupquestions = $DB->get_records_sql($sql, array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id));
        foreach ($questionids as $questionid) {
            if ($questionid) {
                // Convert the raw data of multichoice questions to a real question definition object.
                if (!$offlinequiz->shuffleanswers) {
                    $questiondata[$questionid]->options->shuffleanswers = false;
                }
                $question = question_bank::make_question($questiondata[$questionid]);
                // We only add multichoice questions which are needed for grading.
                if ($question->get_type_name() == 'multichoice' || $question->get_type_name() == 'multichoiceset') {
                    $templateusage->add_question($question, $groupquestions[$question->id]->maxmark);
                }
            }
        }
        // Create attempts for all questions (fixes order of the answers if shuffleanswers is active).
        $templateusage->start_all_questions();
        // Save the template question usage to the DB.
        question_engine::save_questions_usage_by_activity($templateusage);
        // Save the templateusage-ID in the offlinequiz_groups table.
        $group->templateusageid = $templateusage->get_id();
        $DB->set_field('offlinequiz_groups', 'templateusageid', $group->templateusageid, array('id' => $group->id));
    }
    // End else.
    return $templateusage;
}
 /**
  * add the questions to the question usage
  * This is called by the question_attmept class on construct of a new attempt
  *
  * @param \question_usage_by_activity $quba
  *
  * @return array
  */
 public function add_questions_to_quba(\question_usage_by_activity $quba)
 {
     // we need the questionids of our questions
     $questionids = array();
     foreach ($this->qbankOrderedQuestions as $qbankquestion) {
         /** @var activequiz_question $qbankquestion */
         if (!in_array($qbankquestion->getQuestion()->id, $questionids)) {
             $questionids[] = $qbankquestion->getQuestion()->id;
         }
     }
     $questions = question_load_questions($questionids);
     // loop through the ordered question bank questions and add them to the quba
     // object
     $attemptlayout = array();
     foreach ($this->qbankOrderedQuestions as $qbankquestion) {
         $questionid = $qbankquestion->getQuestion()->id;
         $q = \question_bank::make_question($questions[$questionid]);
         $attemptlayout[$qbankquestion->getId()] = $quba->add_question($q, $qbankquestion->getPoints());
     }
     // start the questions in the quba
     $quba->start_all_questions();
     /**
      * return the attempt layout which is a set of ids that are the slot ids from the question engine usage by activity instance
      * these are what are used during an actual attempt rather than the questionid themselves, since the question engine will handle
      * the translation
      */
     return $attemptlayout;
 }
Ejemplo n.º 10
0
 private static function quiz_question_loop($attempt, $quizobj, $quba, $questionsinuse)
 {
     foreach ($quizobj->get_questions() as $i => $questiondata) {
         $questiondata->options->shuffleanswers = 0;
         if ($questiondata->qtype != 'random') {
             $question = question_bank::make_question($questiondata);
         }
         $idstoslots[$i] = $quba->add_question($question, $questiondata->maxmark);
         $questionsinuse[] = $question->id;
     }
     return $idstoslots;
 }