Example #1
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;
}
Example #2
0
 /**
  * Replace a question in an attempt with a new attempt at the same qestion.
  * @param int $slot the questoin to restart.
  * @param int $timestamp the timestamp to record for this action.
  */
 public function process_redo_question($slot, $timestamp)
 {
     global $DB;
     if (!$this->can_question_be_redone_now($slot)) {
         throw new coding_exception('Attempt to restart the question in slot ' . $slot . ' when it is not in a state to be restarted.');
     }
     $qubaids = new \mod_quiz\question\qubaids_for_users_attempts($this->get_quizid(), $this->get_userid());
     $transaction = $DB->start_delegated_transaction();
     $questiondata = $DB->get_record('question', array('id' => $this->slots[$slot]->questionid));
     if ($questiondata->qtype != 'random') {
         $newqusetionid = $questiondata->id;
     } else {
         $randomloader = new \core_question\bank\random_question_loader($qubaids, array());
         $newqusetionid = $randomloader->get_next_question_id($questiondata->category, (bool) $questiondata->questiontext);
         if ($newqusetionid === null) {
             throw new moodle_exception('notenoughrandomquestions', 'quiz', $quizobj->view_url(), $questiondata);
         }
     }
     $newquestion = question_bank::load_question($newqusetionid);
     if ($newquestion->get_num_variants() == 1) {
         $variant = 1;
     } else {
         $variantstrategy = new core_question\engine\variants\least_used_strategy($this->quba, $qubaids);
         $variant = $variantstrategy->choose_variant($newquestion->get_num_variants(), $newquestion->get_variants_selection_seed());
     }
     $newslot = $this->quba->add_question_in_place_of_other($slot, $newquestion);
     $this->quba->start_question($slot);
     $this->quba->set_max_mark($newslot, 0);
     $this->quba->set_question_attempt_metadata($newslot, 'originalslot', $slot);
     question_engine::save_questions_usage_by_activity($this->quba);
     $transaction->allow_commit();
 }
 public function test_existing_question_is_available_but_then_marked_used()
 {
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
     $this->assertTrue($loader->is_question_available($cat->id, 0, $question1->id));
     $this->assertFalse($loader->is_question_available($cat->id, 0, $question1->id));
     $this->assertFalse($loader->is_question_available($cat->id, 0, -1));
 }