Ejemplo n.º 1
0
 /**
  * Test update question flag
  */
 public function test_core_question_update_flag()
 {
     $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
     // Create a question category.
     $cat = $questiongenerator->create_question_category();
     $quba = question_engine::make_questions_usage_by_activity('core_question_update_flag', context_system::instance());
     $quba->set_preferred_behaviour('deferredfeedback');
     $questiondata = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
     $question = question_bank::load_question($questiondata->id);
     $slot = $quba->add_question($question);
     $qa = $quba->get_question_attempt($slot);
     self::setUser($this->student);
     $quba->start_all_questions();
     question_engine::save_questions_usage_by_activity($quba);
     $qubaid = $quba->get_id();
     $questionid = $question->id;
     $qaid = $qa->get_database_id();
     $checksum = md5($qubaid . "_" . $this->student->secret . "_" . $questionid . "_" . $qaid . "_" . $slot);
     $flag = core_question_external::update_flag($qubaid, $questionid, $qaid, $slot, $checksum, true);
     $this->assertTrue($flag['status']);
     // Test invalid checksum.
     try {
         // Using random_string to force failing.
         $checksum = md5($qubaid . "_" . random_string(11) . "_" . $questionid . "_" . $qaid . "_" . $slot);
         core_question_external::update_flag($qubaid, $questionid, $qaid, $slot, $checksum, true);
         $this->fail('Exception expected due to invalid checksum.');
     } catch (moodle_exception $e) {
         $this->assertEquals('errorsavingflags', $e->errorcode);
     }
 }
 /**
  * Run all the question tests for all variants of all questions belonging to
  * a given context.
  *
  * Does output as we go along.
  *
  * @param context $context the context to run the tests for.
  * @return array with two elements:
  *              bool true if all the tests passed, else false.
  *              array of messages relating to the questions with failures.
  */
 public function run_all_tests_for_context(context $context)
 {
     global $DB, $OUTPUT;
     // Load the necessary data.
     $categories = question_category_options(array($context));
     $categories = reset($categories);
     $questiontestsurl = new moodle_url('/question/type/stack/questiontestrun.php');
     if ($context->contextlevel == CONTEXT_COURSE) {
         $questiontestsurl->param('courseid', $context->instanceid);
     } else {
         if ($context->contextlevel == CONTEXT_MODULE) {
             $questiontestsurl->param('cmid', $context->instanceid);
         }
     }
     $allpassed = true;
     $failingtests = array();
     foreach ($categories as $key => $category) {
         list($categoryid) = explode(',', $key);
         echo $OUTPUT->heading($category, 3);
         $questionids = $DB->get_records_menu('question', array('category' => $categoryid, 'qtype' => 'stack'), 'name', 'id,name');
         if (!$questionids) {
             continue;
         }
         echo html_writer::tag('p', stack_string('replacedollarscount', count($questionids)));
         foreach ($questionids as $questionid => $name) {
             $tests = question_bank::get_qtype('stack')->load_question_tests($questionid);
             if (!$tests) {
                 echo $OUTPUT->heading(html_writer::link(new moodle_url($questiontestsurl, array('questionid' => $questionid)), format_string($name)), 4);
                 echo html_writer::tag('p', stack_string('bulktestnotests'));
                 continue;
             }
             $question = question_bank::load_question($questionid);
             $questionname = format_string($name);
             $previewurl = new moodle_url($questiontestsurl, array('questionid' => $questionid));
             if (empty($question->deployedseeds)) {
                 $questionnamelink = html_writer::link($previewurl, $questionname);
                 echo $OUTPUT->heading($questionnamelink, 4);
                 list($ok, $message) = $this->qtype_stack_test_question($question, $tests);
                 if (!$ok) {
                     $allpassed = false;
                     $failingtests[] = $questionnamelink . ': ' . $message;
                 }
             } else {
                 echo $OUTPUT->heading(format_string($name), 4);
                 foreach ($question->deployedseeds as $seed) {
                     $previewurl->param('seed', $seed);
                     $questionnamelink = html_writer::link($previewurl, stack_string('seedx', $seed));
                     echo $OUTPUT->heading($questionnamelink, 4);
                     list($ok, $message) = $this->qtype_stack_test_question($question, $tests, $seed);
                     if (!$ok) {
                         $allpassed = false;
                         $failingtests[] = $questionname . ' ' . $questionnamelink . ': ' . $message;
                     }
                 }
             }
         }
     }
     return array($allpassed, $failingtests);
 }
Ejemplo n.º 3
0
 /**
  * We create two usages, each with two questions, a short-answer marked
  * out of 5, and and essay marked out of 10. We just start these attempts.
  *
  * Then we change the max mark for the short-answer question in one of the
  * usages to 20, using a qubaid_list, and verify.
  *
  * Then we change the max mark for the essay question in the other
  * usage to 2, using a qubaid_join, and verify.
  */
 public function test_set_max_mark_in_attempts()
 {
     // Set up some things the tests will need.
     $this->resetAfterTest();
     $dm = new question_engine_data_mapper();
     // Create the questions.
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $sa = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $essay = $generator->create_question('essay', null, array('category' => $cat->id));
     // Create the first usage.
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage1id = $this->quba->get_id();
     // Create the second usage.
     $this->quba = question_engine::make_questions_usage_by_activity('unit_test', context_system::instance());
     $q = question_bank::load_question($sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $this->process_submission(array('answer' => 'fish'));
     $q = question_bank::load_question($essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $usage2id = $this->quba->get_id();
     // Test set_max_mark_in_attempts with a qubaid_list.
     $usagestoupdate = new qubaid_list(array($usage1id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(10, $quba2->get_question_max_mark(2));
     // Test set_max_mark_in_attempts with a qubaid_join.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => $usage2id));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
     // Test the nothing to do case.
     $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 'qu.id = :usageid', array('usageid' => -1));
     $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
     $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
     $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
     $this->assertEquals(20, $quba1->get_question_max_mark(1));
     $this->assertEquals(10, $quba1->get_question_max_mark(2));
     $this->assertEquals(5, $quba2->get_question_max_mark(1));
     $this->assertEquals(2, $quba2->get_question_max_mark(2));
 }
Ejemplo n.º 4
0
 public function test_question_creation()
 {
     $this->resetAfterTest();
     question_bank::get_qtype('random')->clear_caches_before_testing();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $question2 = $generator->create_question('numerical', null, array('category' => $cat->id));
     $randomquestion = $generator->create_question('random', null, array('category' => $cat->id));
     $expectedids = array($question1->id, $question2->id);
     $actualids = question_bank::get_qtype('random')->get_available_questions_from_category($cat->id, 0);
     sort($expectedids);
     sort($actualids);
     $this->assertEquals($expectedids, $actualids);
     $q = question_bank::load_question($randomquestion->id);
     $this->assertContains($q->id, array($question1->id, $question2->id));
 }
Ejemplo n.º 5
0
 public function test_get_questions_from_categories_with_usage_counts()
 {
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $questiondata2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $questiondata3 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $quba = question_engine::make_questions_usage_by_activity('test', context_system::instance());
     $quba->set_preferred_behaviour('deferredfeedback');
     $question1 = question_bank::load_question($questiondata1->id);
     $question3 = question_bank::load_question($questiondata3->id);
     $quba->add_question($question1);
     $quba->add_question($question1);
     $quba->add_question($question3);
     $quba->start_all_questions();
     question_engine::save_questions_usage_by_activity($quba);
     $this->assertEquals(array($questiondata2->id => 0, $questiondata3->id => 1, $questiondata1->id => 2), question_bank::get_finder()->get_questions_from_categories_with_usage_counts(array($cat->id), new qubaid_list(array($quba->get_id()))));
 }
 public function test_second_attempt_uses_other_dataset()
 {
     global $DB;
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $questiondata = $generator->create_question('calculated', null, array('category' => $cat->id));
     // Create two dataset items.
     $adefinitionid = $DB->get_field_sql("\n                    SELECT qdd.id\n                      FROM {question_dataset_definitions} qdd\n                      JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id\n                     WHERE qd.question = ?\n                       AND qdd.name = ?", array($questiondata->id, 'a'));
     $bdefinitionid = $DB->get_field_sql("\n                    SELECT qdd.id\n                      FROM {question_dataset_definitions} qdd\n                      JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id\n                     WHERE qd.question = ?\n                       AND qdd.name = ?", array($questiondata->id, 'b'));
     $DB->set_field('question_dataset_definitions', 'itemcount', 2, array('id' => $adefinitionid));
     $DB->set_field('question_dataset_definitions', 'itemcount', 2, array('id' => $bdefinitionid));
     $DB->insert_record('question_dataset_items', array('definition' => $adefinitionid, 'itemnumber' => 1, 'value' => 3));
     $DB->insert_record('question_dataset_items', array('definition' => $bdefinitionid, 'itemnumber' => 1, 'value' => 7));
     $DB->insert_record('question_dataset_items', array('definition' => $adefinitionid, 'itemnumber' => 2, 'value' => 6));
     $DB->insert_record('question_dataset_items', array('definition' => $bdefinitionid, 'itemnumber' => 2, 'value' => 4));
     $question = question_bank::load_question($questiondata->id);
     $quba1 = question_engine::make_questions_usage_by_activity('test', context_system::instance());
     $quba1->set_preferred_behaviour('deferredfeedback');
     $slot1 = $quba1->add_question($question);
     $quba1->start_all_questions(new core_question\engine\variants\least_used_strategy($quba1, new qubaid_list(array())));
     question_engine::save_questions_usage_by_activity($quba1);
     $variant1 = $quba1->get_variant($slot1);
     // Second attempt should use the other variant.
     $quba2 = question_engine::make_questions_usage_by_activity('test', context_system::instance());
     $quba2->set_preferred_behaviour('deferredfeedback');
     $slot2 = $quba2->add_question($question);
     $quba2->start_all_questions(new core_question\engine\variants\least_used_strategy($quba1, new qubaid_list(array($quba1->get_id()))));
     question_engine::save_questions_usage_by_activity($quba2);
     $variant2 = $quba2->get_variant($slot2);
     $this->assertNotEquals($variant1, $variant2);
     // Third attempt uses either variant at random.
     $quba3 = question_engine::make_questions_usage_by_activity('test', context_system::instance());
     $quba3->set_preferred_behaviour('deferredfeedback');
     $slot3 = $quba3->add_question($question);
     $quba3->start_all_questions(new core_question\engine\variants\least_used_strategy($quba1, new qubaid_list(array($quba1->get_id(), $quba2->get_id()))));
     $variant3 = $quba3->get_variant($slot3);
     $this->assertTrue($variant3 == $variant1 || $variant3 == $variant2);
 }
 public function test_autosave_with_wrong_seq_number_ignored()
 {
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $question = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     // Start attempt at a shortanswer question.
     $q = question_bank::load_question($question->id);
     $this->start_attempt_at_question($q, 'deferredfeedback', 1);
     $this->check_current_state(question_state::$todo);
     $this->check_current_mark(null);
     $this->check_step_count(1);
     // Process a response and check the expected result.
     $this->process_submission(array('answer' => 'first response'));
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(2);
     $this->save_quba();
     // Now check how that is re-displayed.
     $this->render();
     $this->check_output_contains_text_input('answer', 'first response');
     $this->check_output_contains_hidden_input(':sequencecheck', 2);
     // Process an autosave with a sequence number 1 too small (so from the past).
     $this->load_quba();
     $postdata = $this->response_data_to_post(array('answer' => 'obsolete response'));
     $postdata[$this->quba->get_field_prefix($this->slot) . ':sequencecheck'] = $this->get_question_attempt()->get_sequence_check_count() - 1;
     $this->quba->process_all_autosaves(null, $postdata);
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(2);
     $this->save_quba();
     // Now check how that is re-displayed.
     $this->load_quba();
     $this->render();
     $this->check_output_contains_text_input('answer', 'first response');
     $this->check_output_contains_hidden_input(':sequencecheck', 2);
     $this->delete_quba();
 }
Ejemplo n.º 8
0
 $maxfailedattempts = 3;
 $failedattempts = 0;
 $numberdeployed = 0;
 while ($failedattempts < $maxfailedattempts && $numberdeployed < $deploy) {
     // Genrate a new seed.
     $seed = mt_rand();
     $variantdeployed = false;
     // Reload the question to ensure any new deployed version is included.
     $question = question_bank::load_question($questionid);
     $question->seed = (int) $seed;
     $quba = question_engine::make_questions_usage_by_activity('qtype_stack', $context);
     $quba->set_preferred_behaviour('adaptive');
     $slot = $quba->add_question($question, $question->defaultmark);
     $quba->start_question($slot);
     foreach ($question->deployedseeds as $key => $deployedseed) {
         $qn = question_bank::load_question($questionid);
         $qn->seed = (int) $deployedseed;
         $cn = $qn->get_context();
         $qunote = question_engine::make_questions_usage_by_activity('qtype_stack', $cn);
         $qunote->set_preferred_behaviour('adaptive');
         $slotnote = $qunote->add_question($qn, $qn->defaultmark);
         $qunote->start_question($slotnote);
         // Check if the question note has already been deployed.
         if ($qn->get_question_summary() == $question->get_question_summary()) {
             $variantdeployed = true;
             $failedattempts++;
         }
     }
     if (!$variantdeployed) {
         // Load the list of test cases.
         $testscases = question_bank::get_qtype('stack')->load_question_tests($question->id);
Ejemplo n.º 9
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;
}
Ejemplo n.º 10
0
    public function test_deferred_feedback_plain_attempt_on_last() {
        global $CFG, $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $usercontextid = context_user::instance($USER->id)->id;

        // Create an essay question in the DB.
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
        $cat = $generator->create_question_category();
        $question = $generator->create_question('essay', 'plain', array('category' => $cat->id));

        // Start attempt at the question.
        $q = question_bank::load_question($question->id);
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);

        $this->check_current_state(question_state::$todo);
        $this->check_current_mark(null);
        $this->check_step_count(1);

        // Process a response and check the expected result.

        $this->process_submission(array(
            'answer' => 'Once upon a time there was a frog called Freddy. He lived happily ever after.',
            'answerformat' => FORMAT_PLAIN,
        ));

        $this->check_current_state(question_state::$complete);
        $this->check_current_mark(null);
        $this->check_step_count(2);
        $this->save_quba();

        // Now submit all and finish.
        $this->finish();
        $this->check_current_state(question_state::$needsgrading);
        $this->check_current_mark(null);
        $this->check_step_count(3);
        $this->save_quba();

        // Now start a new attempt based on the old one.
        $this->load_quba();
        $oldqa = $this->get_question_attempt();

        $q = question_bank::load_question($question->id);
        $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
                context_system::instance());
        $this->quba->set_preferred_behaviour('deferredfeedback');
        $this->slot = $this->quba->add_question($q, 1);
        $this->quba->start_question_based_on($this->slot, $oldqa);

        $this->check_current_state(question_state::$complete);
        $this->check_current_mark(null);
        $this->check_step_count(1);
        $this->save_quba();

        // Check the display.
        $this->load_quba();
        $this->render();
        // Test taht no HTML comment has been added to the response.
        $this->assertRegExp('/Once upon a time there was a frog called Freddy. He lived happily ever after.(?!&lt;!--)/', $this->currentoutput);
        // Test for the hash of an empty file area.
        $this->assertNotContains('d41d8cd98f00b204e9800998ecf8427e', $this->currentoutput);
    }
Ejemplo n.º 11
0
    /**
     * Load the definition of another question picked randomly by this question.
     * @param object $questiondata the data defining a random question.
     * @param array $excludedquestions of question ids. We will no pick any
     *      question whose id is in this list.
     * @param bool $allowshuffle if false, then any shuffle option on the
     *      selected quetsion is disabled.
     * @return question_definition|null the definition of the question that was
     *      selected, or null if no suitable question could be found.
     */
    public function choose_other_question($questiondata, $excludedquestions, $allowshuffle = true) {
        $available = $this->get_available_questions_from_category($questiondata->category,
                !empty($questiondata->questiontext));
        shuffle($available);

        foreach ($available as $questionid) {
            if (in_array($questionid, $excludedquestions)) {
                continue;
            }

            $question = question_bank::load_question($questionid, $allowshuffle);
            $this->set_selected_question_name($question, $questiondata->name);
            return $question;
        }
        return null;
    }
Ejemplo n.º 12
0
 /**
  * Choose and load the desired number of questions.
  * @return array of short answer questions.
  */
 public function load_questions()
 {
     if ($this->choose > count($this->availablequestions)) {
         throw new coding_exception('notenoughtshortanswerquestions');
     }
     $questionids = draw_rand_array($this->availablequestions, $this->choose);
     $questions = array();
     foreach ($questionids as $questionid) {
         $questions[] = question_bank::load_question($questionid);
     }
     return $questions;
 }
Ejemplo n.º 13
0
 /**
  * Whether it is possible for another question to depend on this one finishing.
  * Note that the answer is not exact, because of random questions, and sometimes
  * questions cannot be depended upon because of quiz options.
  * @param int $slotnumber the index of the slot in question.
  * @return bool can this question finish naturally during the attempt?
  */
 public function can_finish_during_the_attempt($slotnumber)
 {
     if ($this->quizobj->get_quiz()->shufflequestions || $this->quizobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ) {
         return false;
     }
     if ($this->get_question_type_for_slot($slotnumber) == 'random') {
         return true;
     }
     if (isset($this->slotsinorder[$slotnumber]->canfinish)) {
         return $this->slotsinorder[$slotnumber]->canfinish;
     }
     $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $this->quizobj->get_context());
     $tempslot = $quba->add_question(\question_bank::load_question($this->slotsinorder[$slotnumber]->questionid));
     $quba->set_preferred_behaviour($this->quizobj->get_quiz()->preferredbehaviour);
     $quba->start_all_questions();
     $this->slotsinorder[$slotnumber]->canfinish = $quba->can_question_finish_during_attempt($tempslot);
     return $this->slotsinorder[$slotnumber]->canfinish;
 }
Ejemplo n.º 14
0
 *
 * @package    moodlecore
 * @subpackage questionengine
 * @copyright  Alex Smith {@link http://maths.york.ac.uk/serving_maths} and
 *      numerous contributors.
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


require_once(dirname(__FILE__) . '/../config.php');
require_once($CFG->libdir . '/questionlib.php');
require_once(dirname(__FILE__) . '/previewlib.php');

// Get and validate question id.
$id = required_param('id', PARAM_INT);
$question = question_bank::load_question($id);
require_login();
$category = $DB->get_record('question_categories',
        array('id' => $question->category), '*', MUST_EXIST);
question_require_capability_on($question, 'use');
$PAGE->set_pagelayout('popup');
$PAGE->set_context(get_context_instance_by_id($category->contextid));

// Get and validate display options.
$maxvariant = $question->get_num_variants();
$options = new question_preview_options($question);
$options->load_user_defaults();
$options->set_from_request();
$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, $options));

// Get and validate exitsing preview, or start a new one.
Ejemplo n.º 15
0
 public function test_finish_with_unhandled_autosave_data()
 {
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $question = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     // Start attempt at a shortanswer question.
     $q = question_bank::load_question($question->id);
     $this->start_attempt_at_question($q, 'deferredfeedback', 1);
     $this->check_current_state(question_state::$todo);
     $this->check_current_mark(null);
     $this->check_step_count(1);
     // Process a response and check the expected result.
     $this->process_submission(array('answer' => 'cat'));
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(2);
     $this->save_quba();
     // Now check how that is re-displayed.
     $this->render();
     $this->check_output_contains_text_input('answer', 'cat');
     $this->check_output_contains_hidden_input(':sequencecheck', 2);
     // Process an autosave.
     $this->load_quba();
     $this->process_autosave(array('answer' => 'frog'));
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(3);
     $this->save_quba();
     // Now check how that is re-displayed.
     $this->load_quba();
     $this->render();
     $this->check_output_contains_text_input('answer', 'frog');
     $this->check_output_contains_hidden_input(':sequencecheck', 2);
     // Now finishe the attempt, without having done anything since the autosave.
     $this->finish();
     $this->save_quba();
     // Now check how that has been graded and is re-displayed.
     $this->load_quba();
     $this->check_current_state(question_state::$gradedright);
     $this->check_current_mark(1);
     $this->render();
     $this->check_output_contains_text_input('answer', 'frog', false);
     $this->check_output_contains_hidden_input(':sequencecheck', 4);
     $this->delete_quba();
 }
 public function test_deferred_feedback_html_editor_with_files_attempt_on_last_no_files_uploaded()
 {
     global $CFG, $USER;
     $this->resetAfterTest(true);
     $this->setAdminUser();
     $usercontextid = context_user::instance($USER->id)->id;
     $fs = get_file_storage();
     // Create an essay question in the DB.
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $question = $generator->create_question('essay', 'editorfilepicker', array('category' => $cat->id));
     // Start attempt at the question.
     $q = question_bank::load_question($question->id);
     $this->start_attempt_at_question($q, 'deferredfeedback', 1);
     $this->check_current_state(question_state::$todo);
     $this->check_current_mark(null);
     $this->check_step_count(1);
     // Process a response and check the expected result.
     // First we need to get the draft item ids.
     $this->render();
     if (!preg_match('/env=editor&amp;.*?itemid=(\\d+)&amp;/', $this->currentoutput, $matches)) {
         throw new coding_exception('Editor draft item id not found.');
     }
     $editordraftid = $matches[1];
     if (!preg_match('/env=filemanager&amp;action=browse&amp;.*?itemid=(\\d+)&amp;/', $this->currentoutput, $matches)) {
         throw new coding_exception('File manager draft item id not found.');
     }
     $attachementsdraftid = $matches[1];
     $this->process_submission(array('answer' => 'I refuse to draw you a picture, so there!', 'answerformat' => FORMAT_HTML, 'answer:itemid' => $editordraftid, 'attachments' => $attachementsdraftid));
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(2);
     $this->save_quba();
     // Now submit all and finish.
     $this->finish();
     $this->check_current_state(question_state::$needsgrading);
     $this->check_current_mark(null);
     $this->check_step_count(3);
     $this->save_quba();
     // Now start a new attempt based on the old one.
     $this->load_quba();
     $oldqa = $this->get_question_attempt();
     $q = question_bank::load_question($question->id);
     $this->quba = question_engine::make_questions_usage_by_activity('unit_test', context_system::instance());
     $this->quba->set_preferred_behaviour('deferredfeedback');
     $this->slot = $this->quba->add_question($q, 1);
     $this->quba->start_question_based_on($this->slot, $oldqa);
     $this->check_current_state(question_state::$complete);
     $this->check_current_mark(null);
     $this->check_step_count(1);
     $this->save_quba();
     // Check the display.
     $this->load_quba();
     $this->render();
     $this->assertRegExp('/I refuse to draw you a picture, so there!/', $this->currentoutput);
 }
Ejemplo n.º 17
0
 /**
  * Whether it is possible for another question to depend on this one finishing.
  * Note that the answer is not exact, because of random questions, and sometimes
  * questions cannot be depended upon because of quiz options.
  * @param int $slotnumber the index of the slot in question.
  * @return bool can this question finish naturally during the attempt?
  */
 public function can_finish_during_the_attempt($slotnumber)
 {
     if ($this->quizobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ) {
         return false;
     }
     if ($this->slotsinorder[$slotnumber]->section->shufflequestions) {
         return false;
     }
     if (in_array($this->get_question_type_for_slot($slotnumber), array('random', 'missingtype'))) {
         return \question_engine::can_questions_finish_during_the_attempt($this->quizobj->get_quiz()->preferredbehaviour);
     }
     if (isset($this->slotsinorder[$slotnumber]->canfinish)) {
         return $this->slotsinorder[$slotnumber]->canfinish;
     }
     try {
         $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $this->quizobj->get_context());
         $tempslot = $quba->add_question(\question_bank::load_question($this->slotsinorder[$slotnumber]->questionid));
         $quba->set_preferred_behaviour($this->quizobj->get_quiz()->preferredbehaviour);
         $quba->start_all_questions();
         $this->slotsinorder[$slotnumber]->canfinish = $quba->can_question_finish_during_attempt($tempslot);
         return $this->slotsinorder[$slotnumber]->canfinish;
     } catch (\Exception $e) {
         // If the question fails to start, this should not block editing.
         return false;
     }
 }
 /**
  * Test the various methods that load data for reporting.
  *
  * Since these methods need an expensive set-up, and then only do read-only
  * operations on the data, we use a single method to do the set-up, which
  * calls diffents methods to test each query.
  */
 public function test_reporting_queries()
 {
     // We create two usages, each with two questions, a short-answer marked
     // out of 5, and and essay marked out of 10.
     //
     // In the first usage, the student answers the short-answer
     // question correctly, and enters something in the essay.
     //
     // In the second useage, the student answers the short-answer question
     // wrongly, and leaves the essay blank.
     $this->resetAfterTest();
     $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
     $cat = $generator->create_question_category();
     $this->sa = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $this->essay = $generator->create_question('essay', null, array('category' => $cat->id));
     $this->usageids = array();
     // Create the first usage.
     $q = question_bank::load_question($this->sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $this->allslots[] = $this->slot;
     $this->process_submission(array('answer' => 'cat'));
     $this->process_submission(array('answer' => 'frog', '-submit' => 1));
     $q = question_bank::load_question($this->essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->allslots[] = $this->slot;
     $this->process_submission(array('answer' => '<p>The cat sat on the mat.</p>', 'answerformat' => FORMAT_HTML));
     $this->finish();
     $this->save_quba();
     $this->usageids[] = $this->quba->get_id();
     // Create the second usage.
     $this->quba = question_engine::make_questions_usage_by_activity('unit_test', context_system::instance());
     $q = question_bank::load_question($this->sa->id);
     $this->start_attempt_at_question($q, 'interactive', 5);
     $this->process_submission(array('answer' => 'fish'));
     $q = question_bank::load_question($this->essay->id);
     $this->start_attempt_at_question($q, 'interactive', 10);
     $this->finish();
     $this->save_quba();
     $this->usageids[] = $this->quba->get_id();
     // Set up some things the tests will need.
     $this->dm = new question_engine_data_mapper();
     $this->bothusages = new qubaid_list($this->usageids);
     // Now test the various queries.
     $this->dotest_load_questions_usages_latest_steps();
     $this->dotest_load_questions_usages_question_state_summary();
     $this->dotest_load_questions_usages_where_question_in_state();
     $this->dotest_load_average_marks();
     $this->dotest_sum_usage_marks_subquery();
     $this->dotest_question_attempt_latest_state_view();
 }
Ejemplo n.º 19
0
 /**
  * Load the definition of another question picked randomly by this question.
  * @param object       $questiondata the data defining a random question.
  * @param array        $excludedquestions of question ids. We will no pick any question whose id is in this list.
  * @param bool         $allowshuffle      if false, then any shuffle option on the selected quetsion is disabled.
  * @param null|integer $forcequestionid   if not null then force the picking of question with id $forcequestionid.
  * @throws coding_exception
  * @return question_definition|null the definition of the question that was
  *      selected, or null if no suitable question could be found.
  */
 public function choose_other_question($questiondata, $excludedquestions, $allowshuffle = true, $forcequestionid = null)
 {
     $available = $this->get_available_questions_from_category($questiondata->category, !empty($questiondata->questiontext));
     shuffle($available);
     if ($forcequestionid !== null) {
         $forcedquestionkey = array_search($forcequestionid, $available);
         if ($forcedquestionkey !== false) {
             unset($available[$forcedquestionkey]);
             array_unshift($available, $forcequestionid);
         } else {
             throw new coding_exception('thisquestionidisnotavailable', $forcequestionid);
         }
     }
     foreach ($available as $questionid) {
         if (in_array($questionid, $excludedquestions)) {
             continue;
         }
         $question = question_bank::load_question($questionid, $allowshuffle);
         $this->set_selected_question_name($question, $questiondata->name);
         return $question;
     }
     return null;
 }
Ejemplo n.º 20
0
 /**
  * Create a question_attempt_step from records loaded from the database.
  *
  * For internal use only.
  *
  * @param Iterator $records Raw records loaded from the database.
  * @param int $questionattemptid The id of the question_attempt to extract.
  * @return question_attempt The newly constructed question_attempt.
  */
 public static function load_from_records($records, $questionattemptid, question_usage_observer $observer, $preferredbehaviour)
 {
     $record = $records->current();
     while ($record->questionattemptid != $questionattemptid) {
         $record = $records->next();
         if (!$records->valid()) {
             throw new coding_exception("Question attempt {$questionattemptid} not found in the database.");
         }
         $record = $records->current();
     }
     try {
         $question = question_bank::load_question($record->questionid);
     } catch (Exception $e) {
         // The question must have been deleted somehow. Create a missing
         // question to use in its place.
         $question = question_bank::get_qtype('missingtype')->make_deleted_instance($record->questionid, $record->maxmark + 0);
     }
     $qa = new question_attempt($question, $record->questionusageid, null, $record->maxmark + 0);
     $qa->set_database_id($record->questionattemptid);
     $qa->set_slot($record->slot);
     $qa->variant = $record->variant + 0;
     $qa->minfraction = $record->minfraction + 0;
     $qa->maxfraction = $record->maxfraction + 0;
     $qa->set_flagged($record->flagged);
     $qa->questionsummary = $record->questionsummary;
     $qa->rightanswer = $record->rightanswer;
     $qa->responsesummary = $record->responsesummary;
     $qa->timemodified = $record->timemodified;
     $qa->behaviour = question_engine::make_behaviour($record->behaviour, $qa, $preferredbehaviour);
     $qa->observer = $observer;
     // If attemptstepid is null (which should not happen, but has happened
     // due to corrupt data, see MDL-34251) then the current pointer in $records
     // will not be advanced in the while loop below, and we get stuck in an
     // infinite loop, since this method is supposed to always consume at
     // least one record. Therefore, in this case, advance the record here.
     if (is_null($record->attemptstepid)) {
         $records->next();
     }
     $i = 0;
     $autosavedstep = null;
     $autosavedsequencenumber = null;
     while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) {
         $sequencenumber = $record->sequencenumber;
         $nextstep = question_attempt_step::load_from_records($records, $record->attemptstepid, $qa->get_question()->get_type_name());
         if ($sequencenumber < 0) {
             if (!$autosavedstep) {
                 $autosavedstep = $nextstep;
                 $autosavedsequencenumber = -$sequencenumber;
             } else {
                 // Old redundant data. Mark it for deletion.
                 $qa->observer->notify_step_deleted($nextstep, $qa);
             }
         } else {
             $qa->steps[$i] = $nextstep;
             if ($i == 0) {
                 $question->apply_attempt_state($qa->steps[0]);
             }
             $i++;
         }
         if ($records->valid()) {
             $record = $records->current();
         } else {
             $record = false;
         }
     }
     if ($autosavedstep) {
         if ($autosavedsequencenumber >= $i) {
             $qa->autosavedstep = $autosavedstep;
             $qa->steps[$i] = $qa->autosavedstep;
         } else {
             $qa->observer->notify_step_deleted($autosavedstep, $qa);
         }
     }
     return $qa;
 }
Ejemplo n.º 21
0
 public function test_previously_used_question_not_returned_until_later()
 {
     $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));
     $question2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
     $quba = question_engine::make_questions_usage_by_activity('test', context_system::instance());
     $quba->set_preferred_behaviour('deferredfeedback');
     $question = question_bank::load_question($question2->id);
     $quba->add_question($question);
     $quba->add_question($question);
     $quba->start_all_questions();
     question_engine::save_questions_usage_by_activity($quba);
     $loader = new \core_question\bank\random_question_loader(new qubaid_list(array($quba->get_id())));
     $this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
     $this->assertEquals($question2->id, $loader->get_next_question_id($cat->id, 0));
     $this->assertNull($loader->get_next_question_id($cat->id, 0));
 }
Ejemplo n.º 22
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();
 }
Ejemplo n.º 23
0
    /**
     * Create a question_attempt_step from records loaded from the database.
     *
     * For internal use only.
     *
     * @param Iterator $records Raw records loaded from the database.
     * @param int $questionattemptid The id of the question_attempt to extract.
     * @return question_attempt The newly constructed question_attempt.
     */
    public static function load_from_records($records, $questionattemptid,
            question_usage_observer $observer, $preferredbehaviour) {
        $record = $records->current();
        while ($record->questionattemptid != $questionattemptid) {
            $record = $records->next();
            if (!$records->valid()) {
                throw new coding_exception("Question attempt $questionattemptid not found in the database.");
            }
            $record = $records->current();
        }

        try {
            $question = question_bank::load_question($record->questionid);
        } catch (Exception $e) {
            // The question must have been deleted somehow. Create a missing
            // question to use in its place.
            $question = question_bank::get_qtype('missingtype')->make_deleted_instance(
                    $record->questionid, $record->maxmark + 0);
        }

        $qa = new question_attempt($question, $record->questionusageid,
                null, $record->maxmark + 0);
        $qa->set_database_id($record->questionattemptid);
        $qa->set_slot($record->slot);
        $qa->variant = $record->variant + 0;
        $qa->minfraction = $record->minfraction + 0;
        $qa->set_flagged($record->flagged);
        $qa->questionsummary = $record->questionsummary;
        $qa->rightanswer = $record->rightanswer;
        $qa->responsesummary = $record->responsesummary;
        $qa->timemodified = $record->timemodified;

        $qa->behaviour = question_engine::make_behaviour(
                $record->behaviour, $qa, $preferredbehaviour);

        $i = 0;
        while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) {
            $qa->steps[$i] = question_attempt_step::load_from_records($records, $record->attemptstepid);
            if ($i == 0) {
                $question->apply_attempt_state($qa->steps[0]);
            }
            $i++;
            if ($records->valid()) {
                $record = $records->current();
            } else {
                $record = false;
            }
        }

        $qa->observer = $observer;

        return $qa;
    }