public function test_question_saving_sumwithvariants() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('calculatedsimple', 'sumwithvariants'); $formdata = test_question_maker::get_question_form_data('calculatedsimple', 'sumwithvariants'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; qtype_calculatedsimple_edit_form::mock_submit((array) $formdata); $form = qtype_calculatedsimple_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options'))) { $this->assertAttributeEquals($value, $property, $actualquestiondata); } } foreach ($questiondata->options as $optionname => $value) { if ($optionname != 'answers') { $this->assertAttributeEquals($value, $optionname, $actualquestiondata->options); } } foreach ($questiondata->options->answers as $answer) { $actualanswer = array_shift($actualquestiondata->options->answers); foreach ($answer as $ansproperty => $ansvalue) { if (!in_array($ansproperty, array('id', 'question', 'answerformat'))) { $this->assertAttributeEquals($ansvalue, $ansproperty, $actualanswer); } } } $datasetloader = new qtype_calculated_dataset_loader($actualquestiondata->id); $this->assertEquals(10, $datasetloader->get_number_of_items()); for ($itemno = 1; $itemno <= 10; $itemno++) { $item = $datasetloader->get_values($itemno); $this->assertEquals($formdata->number[($itemno - 1) * 2 + 2], $item['a']); $this->assertEquals($formdata->number[($itemno - 1) * 2 + 1], $item['b']); } }
public function test_question_saving_true() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('truefalse'); $formdata = test_question_maker::get_question_form_data('truefalse'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; qtype_truefalse_edit_form::mock_submit((array) $formdata); $form = qtype_truefalse_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { if (!in_array($property, array('options'))) { $this->assertAttributeEquals($value, $property, $actualquestiondata); } } foreach ($questiondata->options as $optionname => $value) { if (!in_array($optionname, array('trueanswer', 'falseanswer', 'answers'))) { $this->assertAttributeEquals($value, $optionname, $actualquestiondata->options); } } $answerindexes = array(); foreach ($questiondata->options->answers as $ansindex => $answer) { $actualanswer = array_shift($actualquestiondata->options->answers); foreach ($answer as $ansproperty => $ansvalue) { // This question does not use 'answerformat', will ignore it. if (!in_array($ansproperty, array('id', 'question', 'answerformat'))) { $this->assertAttributeEquals($ansvalue, $ansproperty, $actualanswer); } } $answerindexes[$answer->answer] = $ansindex; } $this->assertEquals($questiondata->options->trueanswer, $answerindexes['True']); $this->assertEquals($questiondata->options->falseanswer, $answerindexes['False']); }
public function test_question_saving() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('linkerdesc'); $formdata = test_question_maker::get_question_form_data('linkerdesc'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; qtype_linkerdesc_edit_form::mock_submit((array) $formdata); $form = qtype_linkerdesc_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated'))) { $this->assertAttributeEquals($value, $property, $actualquestiondata); } } }
/** * Load cached statistics from the database. * * @param \qubaid_condition $qubaids Which question usages to load stats for? */ public function get_cached($qubaids) { global $DB; $timemodified = time() - self::TIME_TO_CACHE; $questionstatrecs = $DB->get_records_select('question_statistics', 'hashcode = ? AND timemodified > ?', array($qubaids->get_hash_code(), $timemodified)); $questionids = array(); foreach ($questionstatrecs as $fromdb) { if (is_null($fromdb->variant) && !$fromdb->slot) { $questionids[] = $fromdb->questionid; } } $this->subquestions = question_load_questions($questionids); foreach ($questionstatrecs as $fromdb) { if (is_null($fromdb->variant)) { if ($fromdb->slot) { $this->questionstats[$fromdb->slot]->populate_from_record($fromdb); // Array created in constructor and populated from question. } else { $this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion(); $this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb); $this->subquestionstats[$fromdb->questionid]->question = $this->subquestions[$fromdb->questionid]; } } } // Add cached variant stats to data structure. foreach ($questionstatrecs as $fromdb) { if (!is_null($fromdb->variant)) { if ($fromdb->slot) { $newcalcinstance = new calculated(); $this->questionstats[$fromdb->slot]->variantstats[$fromdb->variant] = $newcalcinstance; $newcalcinstance->question = $this->questionstats[$fromdb->slot]->question; } else { $newcalcinstance = new calculated_for_subquestion(); $this->subquestionstats[$fromdb->questionid]->variantstats[$fromdb->variant] = $newcalcinstance; $newcalcinstance->question = $this->subquestions[$fromdb->questionid]; } $newcalcinstance->populate_from_record($fromdb); } } }
function regrade_selected_attempts($quiz, $attemptids, $groupstudents) { global $DB; require_capability('mod/quiz:regrade', $this->context); if ($groupstudents) { list($usql, $params) = $DB->get_in_or_equal($groupstudents); $where = "qa.userid $usql AND "; } else { $params = array(); $where = ''; } list($asql, $aparams) = $DB->get_in_or_equal($attemptids); $where = "qa.id $asql AND "; $params = array_merge($params, $aparams); $where .= "qa.quiz = ? AND qa.preview = 0"; $params[] = $quiz->id; if (!$attempts = $DB->get_records_sql('SELECT qa.* FROM {quiz_attempts} qa WHERE '. $where, $params)) { print_error('noattemptstoregrade', 'quiz_overview'); } // Fetch all questions $questions = question_load_questions(explode(',',quiz_questions_in_quiz($quiz->questions)), 'qqi.grade AS maxgrade, qqi.id AS instance', '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question'); $updateoverallgrades = array(); foreach($attempts as $attempt) { foreach ($questions as $question) { $changed = regrade_question_in_attempt($question, $attempt, $quiz, true); } $updateoverallgrades[] = $attempt->uniqueid; } $this->check_overall_grades($quiz, array(), $updateoverallgrades); }
/** * Load the questions in this quiz and add some properties to the objects needed in the reports. * * @param object $quiz the quiz. * @return array of questions for this quiz. */ public function load_and_initialise_questions_for_calculations($quiz) { // Load the questions. $questions = quiz_report_get_significant_questions($quiz); $questionids = array(); foreach ($questions as $question) { $questionids[] = $question->id; } $fullquestions = question_load_questions($questionids); foreach ($questions as $qno => $question) { $q = $fullquestions[$question->id]; $q->maxmark = $question->maxmark; $q->slot = $qno; $q->number = $question->number; $questions[$qno] = $q; } return $questions; }
/** * Calculate the stats. * * @param \qubaid_condition $qubaids Which question usages to calculate the stats for? * @return all_calculated_for_qubaid_condition The calculated stats. */ public function calculate($qubaids) { $this->progress->start_progress('', 6); list($lateststeps, $summarks) = $this->get_latest_steps($qubaids); if ($lateststeps) { $this->progress->start_progress('', count($lateststeps), 1); // Compute the statistics of position, and for random questions, work // out which questions appear in which positions. foreach ($lateststeps as $step) { $this->progress->increment_progress(); $israndomquestion = $step->questionid != $this->stats->for_slot($step->slot)->questionid; $breakdownvariants = !$israndomquestion && $this->stats->for_slot($step->slot)->break_down_by_variant(); // If this is a variant we have not seen before create a place to store stats calculations for this variant. if ($breakdownvariants && is_null($this->stats->for_slot($step->slot, $step->variant))) { $question = $this->stats->for_slot($step->slot)->question; $this->stats->initialise_for_slot($step->slot, $question, $step->variant); $this->stats->for_slot($step->slot, $step->variant)->randomguessscore = $this->get_random_guess_score($question); } // Step data walker for main question. $this->initial_steps_walker($step, $this->stats->for_slot($step->slot), $summarks, true, $breakdownvariants); // If this is a random question do the calculations for sub question stats. if ($israndomquestion) { if (is_null($this->stats->for_subq($step->questionid))) { $this->stats->initialise_for_subq($step); } else { if ($this->stats->for_subq($step->questionid)->maxmark != $step->maxmark) { $this->stats->for_subq($step->questionid)->differentweights = true; } } // If this is a variant of this subq we have not seen before create a place to store stats calculations for it. if (is_null($this->stats->for_subq($step->questionid, $step->variant))) { $this->stats->initialise_for_subq($step, $step->variant); } $this->initial_steps_walker($step, $this->stats->for_subq($step->questionid), $summarks, false); // Extra stuff we need to do in this loop for subqs to keep track of where they need to be displayed later. $number = $this->stats->for_slot($step->slot)->question->number; $this->stats->for_subq($step->questionid)->usedin[$number] = $number; // Keep track of which random questions are actually selected from each pool of questions that random // questions are pulled from. $randomselectorstring = $this->stats->for_slot($step->slot)->random_selector_string(); if (!isset($this->randomselectors[$randomselectorstring])) { $this->randomselectors[$randomselectorstring] = array(); } $this->randomselectors[$randomselectorstring][$step->questionid] = $step->questionid; } } $this->progress->end_progress(); foreach ($this->randomselectors as $key => $notused) { ksort($this->randomselectors[$key]); $this->randomselectors[$key] = implode(',', $this->randomselectors[$key]); } $this->stats->subquestions = question_load_questions($this->stats->get_all_subq_ids()); // Compute the statistics for sub questions, if there are any. $this->progress->start_progress('', count($this->stats->subquestions), 1); foreach ($this->stats->subquestions as $qid => $subquestion) { $this->progress->increment_progress(); $subquestion->maxmark = $this->stats->for_subq($qid)->maxmark; $this->stats->for_subq($qid)->question = $subquestion; $this->stats->for_subq($qid)->randomguessscore = $this->get_random_guess_score($subquestion); if ($variants = $this->stats->for_subq($qid)->get_variants()) { foreach ($variants as $variant) { $this->stats->for_subq($qid, $variant)->question = $subquestion; $this->stats->for_subq($qid, $variant)->randomguessscore = $this->get_random_guess_score($subquestion); } $this->stats->for_subq($qid)->sort_variants(); } $this->initial_question_walker($this->stats->for_subq($qid)); if ($this->stats->for_subq($qid)->usedin) { sort($this->stats->for_subq($qid)->usedin, SORT_NUMERIC); $this->stats->for_subq($qid)->positions = implode(',', $this->stats->for_subq($qid)->usedin); } else { $this->stats->for_subq($qid)->positions = ''; } } $this->progress->end_progress(); // Finish computing the averages, and put the sub-question data into the // corresponding questions. // This cannot be a foreach loop because we need to have both // $question and $nextquestion available, but apart from that it is // foreach ($this->questions as $qid => $question). $slots = $this->stats->get_all_slots(); $this->progress->start_progress('', count($slots), 1); while (list(, $slot) = each($slots)) { $this->stats->for_slot($slot)->sort_variants(); $this->progress->increment_progress(); $nextslot = current($slots); $this->initial_question_walker($this->stats->for_slot($slot)); // The rest of this loop is to finish working out where randomly selected question stats should be displayed. if ($this->stats->for_slot($slot)->question->qtype == 'random') { $randomselectorstring = $this->stats->for_slot($slot)->random_selector_string(); if ($nextslot && $randomselectorstring == $this->stats->for_slot($nextslot)->random_selector_string()) { continue; // Next loop iteration. } if (isset($this->randomselectors[$randomselectorstring])) { $this->stats->for_slot($slot)->subquestions = $this->randomselectors[$randomselectorstring]; } } } $this->progress->end_progress(); // Go through the records one more time. $this->progress->start_progress('', count($lateststeps), 1); foreach ($lateststeps as $step) { $this->progress->increment_progress(); $israndomquestion = $this->stats->for_slot($step->slot)->question->qtype == 'random'; $this->secondary_steps_walker($step, $this->stats->for_slot($step->slot), $summarks); if ($israndomquestion) { $this->secondary_steps_walker($step, $this->stats->for_subq($step->questionid), $summarks); } } $this->progress->end_progress(); $slots = $this->stats->get_all_slots(); $this->progress->start_progress('', count($slots), 1); $sumofcovariancewithoverallmark = 0; foreach ($this->stats->get_all_slots() as $slot) { $this->progress->increment_progress(); $this->secondary_question_walker($this->stats->for_slot($slot)); $this->sumofmarkvariance += $this->stats->for_slot($slot)->markvariance; if ($this->stats->for_slot($slot)->covariancewithoverallmark >= 0) { $sumofcovariancewithoverallmark += sqrt($this->stats->for_slot($slot)->covariancewithoverallmark); } } $this->progress->end_progress(); $subqids = $this->stats->get_all_subq_ids(); $this->progress->start_progress('', count($subqids), 1); foreach ($subqids as $subqid) { $this->progress->increment_progress(); $this->secondary_question_walker($this->stats->for_subq($subqid)); } $this->progress->end_progress(); foreach ($this->stats->get_all_slots() as $slot) { if ($sumofcovariancewithoverallmark) { if ($this->stats->for_slot($slot)->negcovar) { $this->stats->for_slot($slot)->effectiveweight = null; } else { $this->stats->for_slot($slot)->effectiveweight = 100 * sqrt($this->stats->for_slot($slot)->covariancewithoverallmark) / $sumofcovariancewithoverallmark; } } else { $this->stats->for_slot($slot)->effectiveweight = null; } } $this->stats->cache($qubaids); // All finished. $this->progress->end_progress(); } return $this->stats; }
public function compute_statistics() { set_time_limit(0); $subquestionstats = array(); // Compute the statistics of position, and for random questions, work // out which questions appear in which positions. foreach ($this->lateststeps as $step) { $this->initial_steps_walker($step, $this->questions[$step->slot]->_stats); // If this is a random question what is the real item being used? if ($step->questionid != $this->questions[$step->slot]->id) { if (!isset($subquestionstats[$step->questionid])) { $subquestionstats[$step->questionid] = $this->make_blank_question_stats(); $subquestionstats[$step->questionid]->questionid = $step->questionid; $subquestionstats[$step->questionid]->allattempts = $this->allattempts; $subquestionstats[$step->questionid]->usedin = array(); $subquestionstats[$step->questionid]->subquestion = true; $subquestionstats[$step->questionid]->differentweights = false; $subquestionstats[$step->questionid]->maxmark = $step->maxmark; } else { if ($subquestionstats[$step->questionid]->maxmark != $step->maxmark) { $subquestionstats[$step->questionid]->differentweights = true; } } $this->initial_steps_walker($step, $subquestionstats[$step->questionid], false); $number = $this->questions[$step->slot]->number; $subquestionstats[$step->questionid]->usedin[$number] = $number; $randomselectorstring = $this->questions[$step->slot]->category . '/' . $this->questions[$step->slot]->questiontext; if (!isset($this->randomselectors[$randomselectorstring])) { $this->randomselectors[$randomselectorstring] = array(); } $this->randomselectors[$randomselectorstring][$step->questionid] = $step->questionid; } } foreach ($this->randomselectors as $key => $notused) { ksort($this->randomselectors[$key]); } // Compute the statistics of question id, if we need any. $this->subquestions = question_load_questions(array_keys($subquestionstats)); foreach ($this->subquestions as $qid => $subquestion) { $subquestion->_stats = $subquestionstats[$qid]; $subquestion->maxmark = $subquestion->_stats->maxmark; $subquestion->_stats->randomguessscore = $this->get_random_guess_score($subquestion); $this->initial_question_walker($subquestion->_stats); if ($subquestionstats[$qid]->differentweights) { // TODO output here really sucks, but throwing is too severe. global $OUTPUT; echo $OUTPUT->notification(get_string('erroritemappearsmorethanoncewithdifferentweight', 'quiz_statistics', $this->subquestions[$qid]->name)); } if ($subquestion->_stats->usedin) { sort($subquestion->_stats->usedin, SORT_NUMERIC); $subquestion->_stats->positions = implode(',', $subquestion->_stats->usedin); } else { $subquestion->_stats->positions = ''; } } // Finish computing the averages, and put the subquestion data into the // corresponding questions. // This cannot be a foreach loop because we need to have both // $question and $nextquestion available, but apart from that it is // foreach ($this->questions as $qid => $question) { reset($this->questions); while (list($slot, $question) = each($this->questions)) { $nextquestion = current($this->questions); $question->_stats->allattempts = $this->allattempts; $question->_stats->positions = $question->number; $question->_stats->maxmark = $question->maxmark; $question->_stats->randomguessscore = $this->get_random_guess_score($question); $this->initial_question_walker($question->_stats); if ($question->qtype == 'random') { $randomselectorstring = $question->category . '/' . $question->questiontext; if ($nextquestion && $nextquestion->qtype == 'random') { $nextrandomselectorstring = $nextquestion->category . '/' . $nextquestion->questiontext; if ($randomselectorstring == $nextrandomselectorstring) { continue; // Next loop iteration } } if (isset($this->randomselectors[$randomselectorstring])) { $question->_stats->subquestions = implode(',', $this->randomselectors[$randomselectorstring]); } } } // Go through the records one more time foreach ($this->lateststeps as $step) { $this->secondary_steps_walker($step, $this->questions[$step->slot]->_stats); if ($this->questions[$step->slot]->qtype == 'random') { $this->secondary_steps_walker($step, $this->subquestions[$step->questionid]->_stats); } } $sumofcovariancewithoverallmark = 0; foreach ($this->questions as $slot => $question) { $this->secondary_question_walker($question->_stats); $this->sumofmarkvariance += $question->_stats->markvariance; if ($question->_stats->covariancewithoverallmark >= 0) { $sumofcovariancewithoverallmark += sqrt($question->_stats->covariancewithoverallmark); $question->_stats->negcovar = 0; } else { $question->_stats->negcovar = 1; } } foreach ($this->subquestions as $subquestion) { $this->secondary_question_walker($subquestion->_stats); } foreach ($this->questions as $question) { if ($sumofcovariancewithoverallmark) { if ($question->_stats->negcovar) { $question->_stats->effectiveweight = null; } else { $question->_stats->effectiveweight = 100 * sqrt($question->_stats->covariancewithoverallmark) / $sumofcovariancewithoverallmark; } } else { $question->_stats->effectiveweight = null; } } }
public function test_question_saving_trims_answers() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('shortanswer'); $formdata = test_question_maker::get_question_form_data('shortanswer'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; $formdata->answer[0] = ' frog '; qtype_shortanswer_edit_form::mock_submit((array) $formdata); $form = qtype_shortanswer_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); $firstsavedanswer = reset($questiondata->options->answers); $this->assertEquals('frog', $firstsavedanswer->answer); }
/** * 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; }
public function test_question_saving_twosubq() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('multianswer'); $formdata = test_question_maker::get_question_form_data('multianswer'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; qtype_multianswer_edit_form::mock_submit((array) $formdata); $form = qtype_multianswer_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options', 'hints', 'stamp'))) { $this->assertAttributeEquals($value, $property, $actualquestiondata); } } foreach ($questiondata->options as $optionname => $value) { if ($optionname != 'questions') { $this->assertAttributeEquals($value, $optionname, $actualquestiondata->options); } } foreach ($questiondata->hints as $hint) { $actualhint = array_shift($actualquestiondata->hints); foreach ($hint as $property => $value) { if (!in_array($property, array('id', 'questionid', 'options'))) { $this->assertAttributeEquals($value, $property, $actualhint); } } } $this->assertObjectHasAttribute('questions', $actualquestiondata->options); $subqpropstoignore = array('id', 'category', 'parent', 'contextid', 'question', 'options', 'stamp', 'version', 'timemodified', 'timecreated'); foreach ($questiondata->options->questions as $subqno => $subq) { $actualsubq = $actualquestiondata->options->questions[$subqno]; foreach ($subq as $subqproperty => $subqvalue) { if (!in_array($subqproperty, $subqpropstoignore)) { $this->assertAttributeEquals($subqvalue, $subqproperty, $actualsubq); } } foreach ($subq->options as $optionname => $value) { if (!in_array($optionname, array('answers'))) { $this->assertAttributeEquals($value, $optionname, $actualsubq->options); } } foreach ($subq->options->answers as $answer) { $actualanswer = array_shift($actualsubq->options->answers); foreach ($answer as $ansproperty => $ansvalue) { // These questions do not use 'answerformat', will ignore it. if (!in_array($ansproperty, array('id', 'question', 'answerformat'))) { $this->assertAttributeEquals($ansvalue, $ansproperty, $actualanswer); } } } } }
/** * 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; }
/** * Load cached statistics from the database. * * @param $qubaids \qubaid_condition * @return array containing two arrays calculated[] and calculated_for_subquestion[]. */ public function get_cached($qubaids) { global $DB; $timemodified = time() - self::TIME_TO_CACHE; $questionstatrecs = $DB->get_records_select('question_statistics', 'hashcode = ? AND timemodified > ?', array($qubaids->get_hash_code(), $timemodified)); $questionids = array(); foreach ($questionstatrecs as $fromdb) { if (!$fromdb->slot) { $questionids[] = $fromdb->questionid; } } $subquestions = question_load_questions($questionids); foreach ($questionstatrecs as $fromdb) { if ($fromdb->slot) { $this->questionstats[$fromdb->slot]->populate_from_record($fromdb); // Array created in constructor and populated from question. } else { $this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion(); $this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb); $this->subquestionstats[$fromdb->questionid]->question = $subquestions[$fromdb->questionid]; } } return array($this->questionstats, $this->subquestionstats); }
function quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions) { global $DB; $timemodified = time() - QUIZ_REPORT_TIME_TO_CACHE_STATS; $params = array('quizid' => $quiz->id, 'groupid' => (int) $currentgroup, 'allattempts' => $useallattempts, 'timemodified' => $timemodified); if (!($quizstats = $DB->get_record_select('quiz_statistics', 'quizid = :quizid AND groupid = :groupid AND allattempts = :allattempts AND timemodified > :timemodified', $params, '*', true))) { list($s, $usingattemptsstring, $quizstats, $qstats) = $this->quiz_stats($nostudentsingroup, $quiz->id, $currentgroup, $groupstudents, $questions, $useallattempts); if ($s) { $toinsert = (object) ((array) $quizstats + $params); $toinsert->timemodified = time(); $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert); foreach ($qstats->questions as $question) { $question->_stats->quizstatisticsid = $quizstats->id; $DB->insert_record('quiz_question_statistics', $question->_stats, false, true); } foreach ($qstats->subquestions as $subquestion) { $subquestion->_stats->quizstatisticsid = $quizstats->id; $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true); } foreach ($qstats->responses as $response) { $response->quizstatisticsid = $quizstats->id; $DB->insert_record('quiz_question_response_stats', $response, false); } } if ($qstats) { $questions = $qstats->questions; $subquestions = $qstats->subquestions; } else { $questions = array(); $subquestions = array(); } } else { //use cached results if ($useallattempts) { $usingattemptsstring = get_string('allattempts', 'quiz_statistics'); $s = $quizstats->allattemptscount; } else { $usingattemptsstring = get_string('firstattempts', 'quiz_statistics'); $s = $quizstats->firstattemptscount; } $subquestions = array(); $questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid' => $quizstats->id), 'subquestion ASC'); $questionstats = quiz_report_index_by_keys($questionstats, array('subquestion', 'questionid')); if (1 < count($questionstats)) { list($mainquestionstats, $subquestionstats) = $questionstats; $subqstofetch = array_keys($subquestionstats); $subquestions = question_load_questions($subqstofetch); foreach (array_keys($subquestions) as $subqid) { $subquestions[$subqid]->_stats = $subquestionstats[$subqid]; } } elseif (count($questionstats)) { $mainquestionstats = $questionstats[0]; } if (count($questionstats)) { foreach (array_keys($questions) as $qid) { $questions[$qid]->_stats = $mainquestionstats[$qid]; } } } return array($quizstats, $questions, $subquestions, $s, $usingattemptsstring); }
function process_states() { global $DB, $OUTPUT; set_time_limit(0); $subquestionstats = array(); foreach ($this->states as $state) { $this->_initial_states_walker($state, $this->questions[$state->question]->_stats); //if this is a random question what is the real item being used? if ($this->questions[$state->question]->qtype == 'random') { if ($realstate = question_get_real_state($state)) { if (!isset($subquestionstats[$realstate->question])) { $subquestionstats[$realstate->question] = $this->stats_init_object(); $subquestionstats[$realstate->question]->usedin = array(); $subquestionstats[$realstate->question]->subquestion = true; $subquestionstats[$realstate->question]->differentweights = false; $subquestionstats[$realstate->question]->maxgrade = $this->questions[$state->question]->maxgrade; } else { if ($subquestionstats[$realstate->question]->maxgrade != $this->questions[$state->question]->maxgrade) { $subquestionstats[$realstate->question]->differentweights = true; } } $this->_initial_states_walker($realstate, $subquestionstats[$realstate->question], false); $number = $this->questions[$state->question]->number; $subquestionstats[$realstate->question]->usedin[$number] = $number; $randomselectorstring = $this->questions[$state->question]->category . '/' . $this->questions[$state->question]->questiontext; if (!isset($this->randomselectors[$randomselectorstring])) { $this->randomselectors[$randomselectorstring] = array(); } $this->randomselectors[$randomselectorstring][$realstate->question] = $realstate->question; } } } foreach ($this->randomselectors as $key => $randomselector) { ksort($this->randomselectors[$key]); } $this->subquestions = question_load_questions(array_keys($subquestionstats)); foreach (array_keys($this->subquestions) as $qid) { $this->subquestions[$qid]->_stats = $subquestionstats[$qid]; $this->subquestions[$qid]->_stats->questionid = $qid; $this->subquestions[$qid]->maxgrade = $this->subquestions[$qid]->_stats->maxgrade; $this->_initial_question_walker($this->subquestions[$qid]->_stats); if ($subquestionstats[$qid]->differentweights) { echo $OUTPUT->notification(get_string('erroritemappearsmorethanoncewithdifferentweight', 'quiz_statistics', $this->subquestions[$qid]->name)); } if ($this->subquestions[$qid]->_stats->usedin) { sort($this->subquestions[$qid]->_stats->usedin, SORT_NUMERIC); $this->subquestions[$qid]->_stats->positions = join($this->subquestions[$qid]->_stats->usedin, ','); } else { $this->subquestions[$qid]->_stats->positions = ''; } } reset($this->questions); do { list($qid, $question) = each($this->questions); $nextquestion = current($this->questions); $this->questions[$qid]->_stats->questionid = $qid; $this->questions[$qid]->_stats->positions = $this->questions[$qid]->number; $this->questions[$qid]->_stats->maxgrade = $question->maxgrade; $this->_initial_question_walker($this->questions[$qid]->_stats); if ($question->qtype == 'random') { $randomselectorstring = $question->category . '/' . $question->questiontext; if ($nextquestion) { $nextrandomselectorstring = $nextquestion->category . '/' . $nextquestion->questiontext; if ($nextquestion->qtype == 'random' && $randomselectorstring == $nextrandomselectorstring) { continue; //next loop iteration } } if (isset($this->randomselectors[$randomselectorstring])) { $question->_stats->subquestions = join($this->randomselectors[$randomselectorstring], ','); } } } while ($nextquestion); //go through the records one more time foreach ($this->states as $state) { $this->_secondary_states_walker($state, $this->questions[$state->question]->_stats); if ($this->questions[$state->question]->qtype == 'random') { if ($realstate = question_get_real_state($state)) { $this->_secondary_states_walker($realstate, $this->subquestions[$realstate->question]->_stats); } } } $sumofcovariancewithoverallgrade = 0; foreach (array_keys($this->questions) as $qid) { $this->_secondary_question_walker($this->questions[$qid]->_stats); $this->sumofgradevariance += $this->questions[$qid]->_stats->gradevariance; if ($this->questions[$qid]->_stats->covariancewithoverallgrade >= 0) { $sumofcovariancewithoverallgrade += sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade); $this->questions[$qid]->_stats->negcovar = 0; } else { $this->questions[$qid]->_stats->negcovar = 1; } } foreach (array_keys($this->subquestions) as $qid) { $this->_secondary_question_walker($this->subquestions[$qid]->_stats); } foreach (array_keys($this->questions) as $qid) { if ($sumofcovariancewithoverallgrade) { if ($this->questions[$qid]->_stats->negcovar) { $this->questions[$qid]->_stats->effectiveweight = null; } else { $this->questions[$qid]->_stats->effectiveweight = 100 * sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade) / $sumofcovariancewithoverallgrade; } } else { $this->questions[$qid]->_stats->effectiveweight = null; } } }
/** * Load the cached statistics from the database. * * @param object $quiz the quiz settings * @param int $currentgroup the current group. 0 for none. * @param bool $nostudentsingroup true if there a no students. * @param bool $useallattempts use all attempts, or just first attempts. * @param array $groupstudents students in this group. * @param array $questions question definitions. * @return array with 4 elements: * - $quizstats The statistics for overall attempt scores. * - $questions The questions, with an additional _stats field. * - $subquestions The subquestions, if any, with an additional _stats field. * - $s Number of attempts included in the stats. * If there is no cached data in the database, returns an array of four nulls. */ protected function try_loading_cached_stats($quiz, $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions) { global $DB; $timemodified = time() - self::TIME_TO_CACHE_STATS; $quizstats = $DB->get_record_select('quiz_statistics', 'quizid = ? AND groupid = ? AND allattempts = ? AND timemodified > ?', array($quiz->id, $currentgroup, $useallattempts, $timemodified)); if (!$quizstats) { // No cached data found. return array(null, $questions, null, null); } if ($useallattempts) { $s = $quizstats->allattemptscount; } else { $s = $quizstats->firstattemptscount; } $subquestions = array(); $questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid' => $quizstats->id)); $subquestionstats = array(); foreach ($questionstats as $stat) { if ($stat->slot) { $questions[$stat->slot]->_stats = $stat; } else { $subquestionstats[$stat->questionid] = $stat; } } if (!empty($subquestionstats)) { $subqstofetch = array_keys($subquestionstats); $subquestions = question_load_questions($subqstofetch); foreach ($subquestions as $subqid => $subq) { $subquestions[$subqid]->_stats = $subquestionstats[$subqid]; $subquestions[$subqid]->maxmark = $subq->defaultmark; } } return array($quizstats, $questions, $subquestions, $s); }
public function test_question_saving_foursubq() { $this->resetAfterTest(true); $this->setAdminUser(); $questiondata = test_question_maker::get_question_data('match'); $formdata = test_question_maker::get_question_form_data('match'); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $generator->create_question_category(array()); $formdata->category = "{$cat->id},{$cat->contextid}"; qtype_match_edit_form::mock_submit((array) $formdata); $form = qtype_match_test_helper::get_question_editing_form($cat, $questiondata); $this->assertTrue($form->is_validated()); $fromform = $form->get_data(); $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); $actualquestionsdata = question_load_questions(array($returnedfromsave->id)); $actualquestiondata = end($actualquestionsdata); foreach ($questiondata as $property => $value) { if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options', 'stamp'))) { $this->assertAttributeEquals($value, $property, $actualquestiondata); } } foreach ($questiondata->options as $optionname => $value) { if ($optionname != 'subquestions') { $this->assertAttributeEquals($value, $optionname, $actualquestiondata->options); } } $this->assertObjectHasAttribute('subquestions', $actualquestiondata->options); $subqpropstoignore = array('id'); foreach ($questiondata->options->subquestions as $subq) { $actualsubq = array_shift($actualquestiondata->options->subquestions); foreach ($subq as $subqproperty => $subqvalue) { if (!in_array($subqproperty, $subqpropstoignore)) { $this->assertAttributeEquals($subqvalue, $subqproperty, $actualsubq); } } } }