protected function load_extra_data() { if (count($this->rawdata) === 0) { return; } $qubaids = $this->get_qubaids_condition(); $dm = new question_engine_data_mapper(); $this->questionusagesbyactivity = $dm->load_questions_usages_by_activity($qubaids); // Insert an extra field in attempt data and extra rows where necessary. $newrawdata = array(); foreach ($this->rawdata as $attempt) { $maxtriesinanyslot = 1; foreach ($this->questionusagesbyactivity[$attempt->usageid]->get_slots() as $slot) { $tries = $this->get_no_of_tries($attempt, $slot); $maxtriesinanyslot = max($maxtriesinanyslot, $tries); } for ($try = 1; $try <= $maxtriesinanyslot; $try++) { $newtablerow = clone $attempt; $newtablerow->lasttryforallparts = $try == $maxtriesinanyslot; if ($try !== $maxtriesinanyslot) { $newtablerow->state = quiz_attempt::IN_PROGRESS; } $newtablerow->try = $try; $newrawdata[] = $newtablerow; if ($this->options->whichtries == question_attempt::FIRST_TRY) { break; } } } $this->rawdata = $newrawdata; }
/** * 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)); }
public function load_latest_steps($attempts) { $qubaids = array(); foreach ($attempts as $attempt) { $qubaids[] = $attempt->uniqueid; } $dm = new question_engine_data_mapper(); $qubaidcondition = new qubaid_list($qubaids); $slots = array_filter(explode(',', $attempt->layout)); $lateststeps = $dm->load_questions_usages_latest_steps($qubaidcondition, $slots); $this->lateststeps = array(); foreach ($lateststeps as $step) { $this->lateststeps[$step->questionusageid][$step->slot] = $step; } }
protected function dotest_question_attempt_latest_state_view() { global $DB; list($inlineview, $viewparams) = $this->dm->question_attempt_latest_state_view( 'lateststate', $this->bothusages); $rawstates = $DB->get_records_sql(" SELECT lateststate.questionattemptid, qu.id AS questionusageid, lateststate.slot, lateststate.questionid, lateststate.maxmark, lateststate.sequencenumber, lateststate.state FROM {question_usages} qu LEFT JOIN $inlineview ON lateststate.questionusageid = qu.id WHERE qu.id IN ({$this->usageids[0]}, {$this->usageids[1]})", $viewparams); $states = array(); foreach ($rawstates as $state) { $states[$state->questionusageid][$state->slot] = $state; unset($state->questionattemptid); unset($state->questionusageid); unset($state->slot); } $state = $states[$this->usageids[0]][$this->allslots[0]]; $this->assertEquals((object) array( 'questionid' => $this->sa->id, 'maxmark' => '5.0000000', 'sequencenumber' => 2, 'state' => (string) question_state::$gradedright, ), $state); $state = $states[$this->usageids[0]][$this->allslots[1]]; $this->assertEquals((object) array( 'questionid' => $this->essay->id, 'maxmark' => '10.0000000', 'sequencenumber' => 2, 'state' => (string) question_state::$needsgrading, ), $state); $state = $states[$this->usageids[1]][$this->allslots[0]]; $this->assertEquals((object) array( 'questionid' => $this->sa->id, 'maxmark' => '5.0000000', 'sequencenumber' => 2, 'state' => (string) question_state::$gradedwrong, ), $state); $state = $states[$this->usageids[1]][$this->allslots[1]]; $this->assertEquals((object) array( 'questionid' => $this->essay->id, 'maxmark' => '10.0000000', 'sequencenumber' => 1, 'state' => (string) question_state::$gaveup, ), $state); }
/** * Add an average grade over the attempts of a set of users. * @param string $label the title ot use for this row. * @param array $users the users to average over. */ protected function add_average_row($label, $users) { global $DB; list($fields, $from, $where, $params) = $this->base_sql($users); $record = $DB->get_record_sql("\n SELECT AVG(quiza.sumgrades) AS grade, COUNT(quiza.sumgrades) AS numaveraged\n FROM {$from}\n WHERE {$where}", $params); $record->grade = quiz_rescale_grade($record->grade, $this->quiz, false); if ($this->is_downloading()) { $namekey = 'lastname'; } else { $namekey = 'fullname'; } $averagerow = array($namekey => $label, 'sumgrades' => $this->format_average($record), 'feedbacktext' => strip_tags(quiz_report_feedback_for_grade($record->grade, $this->quiz->id, $this->context))); if ($this->options->slotmarks) { $dm = new question_engine_data_mapper(); $qubaids = new qubaid_join($from, 'quiza.uniqueid', $where, $params); $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions)); $averagerow += $this->format_average_grade_for_questions($avggradebyq); } $this->add_data_keyed($averagerow); }
/** * Analyse all the response data for for all the specified attempts at * this question. * @param $qubaids which attempts to consider. */ public function analyse($qubaids) { // Load data. $dm = new question_engine_data_mapper(); $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids); // Analyse it. foreach ($questionattempts as $qa) { $this->add_data_from_one_attempt($qa); } $this->loaded = true; }
/** * If the request seems valid, update the flag state of a question attempt. * Throws exceptions if this is not a valid update request. * @param int $qubaid the question usage id. * @param int $questionid the question id. * @param int $sessionid the question_attempt id. * @param string $checksum checksum, as computed by {@link get_toggle_checksum()} * corresponding to the last three arguments. * @param bool $newstate the new state of the flag. true = flagged. */ public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) { // Check the checksum - it is very hard to know who a question session belongs // to, so we require that checksum parameter is matches an md5 hash of the // three ids and the users username. Since we are only updating a flag, that // probably makes it sufficiently difficult for malicious users to toggle // other users flags. if ($checksum != self::get_toggle_checksum($qubaid, $questionid, $qaid, $slot)) { throw new moodle_exception('errorsavingflags', 'question'); } $dm = new question_engine_data_mapper(); $dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate); }
/** * Get the latest step data from the db, from which we will calculate stats. * * @param \qubaid_condition $qubaids Which question usages to get the latest steps for? * @return array with two items * - $lateststeps array of latest step data for the question usages * - $summarks array of total marks for each usage, indexed by usage id */ protected function get_latest_steps($qubaids) { $dm = new \question_engine_data_mapper(); $fields = " qas.id,\n qa.questionusageid,\n qa.questionid,\n qa.variant,\n qa.slot,\n qa.maxmark,\n qas.fraction * qa.maxmark as mark"; $lateststeps = $dm->load_questions_usages_latest_steps($qubaids, $this->stats->get_all_slots(), $fields); $summarks = array(); if ($lateststeps) { foreach ($lateststeps as $step) { if (!isset($summarks[$step->questionusageid])) { $summarks[$step->questionusageid] = 0; } $summarks[$step->questionusageid] += $step->mark; } } return array($lateststeps, $summarks); }
/** * Add the information about the latest state of the question with slot * $slot to the query. * * The extra information is added as a join to a * 'table' with alias qa$slot, with columns that are a union of * the columns of the question_attempts and question_attempts_states tables. * * @param int $slot the question to add information for. */ protected function add_latest_state_join($slot) { $alias = 'qa' . $slot; $fields = $this->get_required_latest_state_fields($slot, $alias); if (!$fields) { return; } // This condition roughly filters the list of attempts to be considered. // It is only used in a subselect to help crappy databases (see MDL-30122) // therefore, it is better to use a very simple join, which may include // too many records, than to do a super-accurate join. $qubaids = new qubaid_join("{quiz_attempts} {$alias}quiza", "{$alias}quiza.uniqueid", "{$alias}quiza.quiz = :{$alias}quizid", array("{$alias}quizid" => $this->sql->params['quizid'])); $dm = new question_engine_data_mapper(); list($inlineview, $viewparams) = $dm->question_attempt_latest_state_view($alias, $qubaids); $this->sql->fields .= ",\n$fields"; $this->sql->from .= "\nLEFT JOIN $inlineview ON " . "$alias.questionusageid = quiza.uniqueid AND $alias.slot = :{$alias}slot"; $this->sql->params[$alias . 'slot'] = $slot; $this->sql->params = array_merge($this->sql->params, $viewparams); }
// Stack is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Stack. If not, see <http://www.gnu.org/licenses/>. /** * Handles ajax requests to validate the input. */ define('AJAX_SCRIPT', true); require_once __DIR__ . '/../../../../../config.php'; require_once $CFG->libdir . '/questionlib.php'; require_once __DIR__ . '/../options.class.php'; require_once __DIR__ . '/inputbase.class.php'; $qaid = required_param('qaid', PARAM_INT); $inputname = required_param('name', PARAM_ALPHANUMEXT); $inputvalue = required_param('input', PARAM_RAW); if (!isloggedin()) { die; } // This should not be necessary, but the TeX filter requires it, because it uses $OUTPUT. $PAGE->set_context(context_system::instance()); $dm = new question_engine_data_mapper(); $qa = $dm->load_question_attempt($qaid); $question = $qa->get_question(); $input = $question->inputs[$inputname]; $state = $question->get_input_state($inputname, $input->maxima_to_response_array($inputvalue)); $result = array('input' => $inputvalue, 'status' => $state->status, 'message' => $input->render_validation($state, $qa->get_qt_field_name($inputname))); header('Content-type: application/json; charset=utf-8'); echo json_encode($result);
function quiz_update_all_attempt_sumgrades($quiz) { global $DB; $dm = new question_engine_data_mapper(); $timenow = time(); $sql = "UPDATE {quiz_attempts}\n SET\n timemodified = :timenow,\n sumgrades = (\n {$dm->sum_usage_marks_subquery('uniqueid')}\n )\n WHERE quiz = :quizid AND timefinish <> 0"; $DB->execute($sql, array('timenow' => $timenow, 'quizid' => $quiz->id)); }
/** * Write all the changes we have recorded to the database. * @param question_engine_data_mapper $dm the mapper to use to update the database. */ public function save(question_engine_data_mapper $dm) { $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context()); foreach ($this->stepsmodified as $stepinfo) { list($step, $questionattemptid, $seq) = $stepinfo; $dm->update_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context()); } foreach ($this->stepsadded as $stepinfo) { list($step, $questionattemptid, $seq) = $stepinfo; $dm->insert_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context()); } foreach ($this->attemptsadded as $qa) { $dm->insert_question_attempt($qa, $this->quba->get_owning_context()); } foreach ($this->attemptsmodified as $qa) { $dm->update_question_attempt($qa); } if ($this->modified) { $dm->update_questions_usage_by_activity($this->quba); } }
/** * @param $qubaids \qubaid_condition * @return array with three items * - $lateststeps array of latest step data for the question usages * - $summarks array of total marks for each usage, indexed by usage id * - $summarksavg the average of the total marks over all the usages */ protected function get_latest_steps($qubaids) { $dm = new \question_engine_data_mapper(); $fields = " qas.id,\n qa.questionusageid,\n qa.questionid,\n qa.slot,\n qa.maxmark,\n qas.fraction * qa.maxmark as mark"; $lateststeps = $dm->load_questions_usages_latest_steps($qubaids, array_keys($this->questionstats), $fields); $summarks = array(); if ($lateststeps) { foreach ($lateststeps as $step) { if (!isset($summarks[$step->questionusageid])) { $summarks[$step->questionusageid] = 0; } $summarks[$step->questionusageid] += $step->mark; } $summarksavg = array_sum($summarks) / count($summarks); } else { $summarksavg = null; } return array($lateststeps, $summarks, $summarksavg); }
/** * Get a list of usage ids where the question with slot $slot, and optionally * also with question id $questionid, is in summary state $summarystate. Also * return the total count of such states. * * Only a subset of the ids can be returned by using $orderby, $limitfrom and * $limitnum. A special value 'random' can be passed as $orderby, in which case * $limitfrom is ignored. * * @param int $slot The slot for the questions you want to konw about. * @param int $questionid (optional) Only return attempts that were of this specific question. * @param string $summarystate 'all', 'needsgrading', 'autograded' or 'manuallygraded'. * @param string $orderby 'random', 'date', 'student' or 'idnumber'. * @param int $page implements paging of the results. * Ignored if $orderby = random or $pagesize is null. * @param int $pagesize implements paging of the results. null = all. */ protected function get_usage_ids_where_question_in_state($summarystate, $slot, $questionid = null, $orderby = 'random', $page = 0, $pagesize = null) { global $CFG, $DB; $dm = new question_engine_data_mapper(); if ($pagesize && $orderby != 'random') { $limitfrom = $page * $pagesize; } else { $limitfrom = 0; } $qubaids = $this->get_qubaids_condition(); $params = array(); if ($orderby == 'date') { list($statetest, $params) = $dm->in_summary_state_test('manuallygraded', false, 'mangrstate'); $orderby = "(\n SELECT MAX(sortqas.timecreated)\n FROM {question_attempt_steps} sortqas\n WHERE sortqas.questionattemptid = qa.id\n AND sortqas.state {$statetest}\n )"; } else { if ($orderby == 'studentfirstname' || $orderby == 'studentlastname' || $orderby == 'idnumber') { $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id "; // For name sorting, map orderby form value to // actual column names; 'idnumber' maps naturally switch ($orderby) { case "studentlastname": $orderby = "u.lastname, u.firstname"; break; case "studentfirstname": $orderby = "u.firstname, u.lastname"; break; } } } return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate, $slot, $questionid, $orderby, $params, $limitfrom, $pagesize); }
/** * Add the information about the latest state of the question with slot * $slot to the query. * * The extra information is added as a join to a * 'table' with alias qa$slot, with columns that are a union of * the columns of the question_attempts and question_attempts_states tables. * * @param int $slot the question to add information for. */ protected function add_latest_state_join($slot) { $alias = 'qa' . $slot; $fields = $this->get_required_latest_state_fields($slot, $alias); if (!$fields) { return; } $dm = new question_engine_data_mapper(); $inlineview = $dm->question_attempt_latest_state_view($alias); $this->sql->fields .= ",\n{$fields}"; $this->sql->from .= "\nLEFT JOIN {$inlineview} ON " . "{$alias}.questionusageid = quiza.uniqueid AND {$alias}.slot = :{$alias}slot"; $this->sql->params[$alias . 'slot'] = $slot; }
/** * Display analysis of a particular question in this quiz. * @param object $question the row from the question table for the question to analyse. */ public function display_analysis($question) { get_question_options($question); $this->display_question_information($question); $dm = new question_engine_data_mapper(); $this->attempts = $dm->load_attempts_at_question($question->id, $this->qubaids); // Setup useful internal arrays for report generation. $this->inputs = array_keys($question->inputs); $this->prts = array_keys($question->prts); // TODO: change this to be a list of all *deployed* notes, not just those *used*. $qnotes = array(); foreach ($this->attempts as $qa) { $q = $qa->get_question(); $qnotes[$q->get_question_summary()] = true; } $this->qnotes = array_keys($qnotes); // Compute results. list($results, $answernoteresults, $answernoteresultsraw) = $this->input_report(); list($validresults, $invalidresults) = $this->input_report_separate(); // Display the results. // Overall results. $i = 0; $list = ''; $tablehead = array(); foreach ($this->qnotes as $qnote) { $list .= html_writer::tag('li', stack_ouput_castext($qnote)); $i++; $tablehead[] = $i; } $tablehead[] = format_string(get_string('questionreportingtotal', 'quiz_stack')); $tablehead = array_merge(array(''), $tablehead, $tablehead); echo html_writer::tag('p', get_string('notesused', 'quiz_stack')); echo html_writer::tag('ol', $list); // Complete anwernotes. $inputstable = new html_table(); $inputstable->head = $tablehead; $data = array(); foreach ($answernoteresults as $prt => $anotedata) { if (count($answernoteresults) > 1) { $inputstable->data[] = array(html_writer::tag('b', $this->prts[$prt])); } $cstats = $this->column_stats($anotedata); foreach ($anotedata as $anote => $a) { $inputstable->data[] = array_merge(array($anote), $a, array(array_sum($a)), $cstats[$anote]); } } echo html_writer::tag('p', get_string('completenotes', 'quiz_stack')); echo html_writer::table($inputstable); // Split anwernotes. $inputstable = new html_table(); $inputstable->head = $tablehead; foreach ($answernoteresultsraw as $prt => $anotedata) { if (count($answernoteresultsraw) > 1) { $inputstable->data[] = array(html_writer::tag('b', $this->prts[$prt])); } $cstats = $this->column_stats($anotedata); foreach ($anotedata as $anote => $a) { $inputstable->data[] = array_merge(array($anote), $a, array(array_sum($a)), $cstats[$anote]); } } echo html_writer::tag('p', get_string('splitnotes', 'quiz_stack')); echo html_writer::table($inputstable); // Maxima analysis. $maxheader = array(); $maxheader[] = "STACK input data for the question '" . $question->name . "'"; $maxheader[] = new moodle_url($this->get_base_url(), array('questionid' => $question->id)); $maxheader[] = "Data generated: " . date("Y-m-d H:i:s"); $maximacode = $this->maxima_comment($maxheader); $maximacode .= "\ndisplay2d:true\$\nload(\"stackreporting\")\$\n"; $maximacode .= "stackdata:[]\$\n"; $variants = array(); foreach ($this->qnotes as $qnote) { $variants[] = '"' . $qnote . '"'; } $inputs = array(); foreach ($this->inputs as $input) { $inputs[] = $input; } $anymaximadata = false; // Results for each question note. foreach ($this->qnotes as $qnote) { echo html_writer::tag('h2', get_string('variantx', 'quiz_stack') . stack_ouput_castext($qnote)); $inputstable = new html_table(); $inputstable->attributes['class'] = 'generaltable stacktestsuite'; $inputstable->head = array_merge(array(get_string('questionreportingsummary', 'quiz_stack'), '', get_string('questionreportingscore', 'quiz_stack')), $this->prts); foreach ($results[$qnote] as $dsummary => $summary) { foreach ($summary as $key => $res) { $inputstable->data[] = array_merge(array($dsummary, $res['count'], $res['fraction']), $res['answernotes']); } } echo html_writer::table($inputstable); // Separate out inputs and look at validity. $validresultsdata = array(); foreach ($this->inputs as $input) { $inputstable = new html_table(); $inputstable->attributes['class'] = 'generaltable stacktestsuite'; $inputstable->head = array($input, '', '', ''); foreach ($validresults[$qnote][$input] as $key => $res) { $validresultsdata[$input][] = $key; $inputstable->data[] = array($key, $res, get_string('inputstatusnamevalid', 'qtype_stack'), ''); $inputstable->rowclasses[] = 'pass'; } foreach ($invalidresults[$qnote][$input] as $key => $res) { $inputstable->data[] = array($key, $res[0], get_string('inputstatusnameinvalid', 'qtype_stack'), $res[1]); $inputstable->rowclasses[] = 'fail'; } echo html_writer::table($inputstable); } // Maxima analysis. $maximacode .= "\n/* " . $qnote . ' */ ' . "\n"; foreach ($this->inputs as $input) { if (array_key_exists($input, $validresultsdata)) { $maximacode .= $this->maxima_list_create($validresultsdata[$input], $input); $anymaximadata = true; } } $maximacode .= "stackdata:append(stackdata,[[" . implode(',', $inputs) . "]])\$\n"; } // Maxima analysis at the end. if ($anymaximadata) { $maximacode .= "\n/* Reset input names */\nkill(" . implode(',', $inputs) . ")\$\n"; $maximacode .= $this->maxima_list_create($variants, 'variants'); $maximacode .= $this->maxima_list_create($inputs, 'inputs'); $maximacode .= "\n/* Perform the analysis. */\nstack_analysis(stackdata)\$\n"; echo html_writer::tag('h3', get_string('maximacode', 'quiz_stack')); echo html_writer::tag('p', get_string('offlineanalysis', 'quiz_stack')); $rows = count(explode("\n", $maximacode)) + 2; echo html_writer::tag('textarea', $maximacode, array('readonly' => 'readonly', 'wrap' => 'virtual', 'rows' => $rows, 'cols' => '160')); } }
/** * Update the sumgrades field of the results in an offline quiz. * * @param object $offlinequiz The offlinequiz. */ function offlinequiz_update_all_attempt_sumgrades($offlinequiz) { global $DB; $dm = new question_engine_data_mapper(); $timenow = time(); $sql = "UPDATE {offlinequiz_results}\n SET timemodified = :timenow,\n sumgrades = (\n {$dm->sum_usage_marks_subquery('usageid')}\n )\n WHERE offlinequizid = :offlinequizid\n AND timefinish <> 0"; $DB->execute($sql, array('timenow' => $timenow, 'offlinequizid' => $offlinequiz->id)); }
/** * Load the average grade for each question, averaged over particular users. * @param array $userids the user ids to average over. */ protected function load_average_question_grades($userids) { global $DB; $qmfilter = ''; if ($this->quiz->attempts != 1) { $qmfilter = '(' . quiz_report_qm_filter_select($this->quiz, 'quiza') . ') AND '; } list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u'); $params['quizid'] = $this->quiz->id; $qubaids = new qubaid_join( '{quiz_attempts} quiza', 'quiza.uniqueid', "quiza.userid $usql AND quiza.quiz = :quizid", $params); $dm = new question_engine_data_mapper(); return $dm->load_average_marks($qubaids, array_keys($this->questions)); }
/** * Update the sumgrades field of the attempts at a quiz. * * @param object $quiz a quiz. */ function quiz_update_all_attempt_sumgrades($quiz) { global $DB; $dm = new question_engine_data_mapper(); $timenow = time(); $sql = "UPDATE {quiz_attempts} SET timemodified = :timenow, sumgrades = ( {$dm->sum_usage_marks_subquery('uniqueid')} ) WHERE quiz = :quizid AND state = :finishedstate"; $DB->execute($sql, array('timenow' => $timenow, 'quizid' => $quiz->id, 'finishedstate' => quiz_attempt::FINISHED)); }
/** * Write all the changes we have recorded to the database. * @param question_engine_data_mapper $dm the mapper to use to update the database. */ public function save(question_engine_data_mapper $dm) { $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context()); // Initially an array of array of question_attempt_step_objects. // Built as a nested array for efficiency, then flattened. $stepdata = array(); foreach ($this->stepsmodified as $stepinfo) { list($step, $questionattemptid, $seq) = $stepinfo; $stepdata[] = $dm->update_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context()); } foreach ($this->stepsadded as $stepinfo) { list($step, $questionattemptid, $seq) = $stepinfo; $stepdata[] = $dm->insert_question_attempt_step($step, $questionattemptid, $seq, $this->quba->get_owning_context()); } foreach ($this->attemptsadded as $qa) { $stepdata[] = $dm->insert_question_attempt($qa, $this->quba->get_owning_context()); } foreach ($this->attemptsmodified as $qa) { $dm->update_question_attempt($qa); } if ($this->modified) { $dm->update_questions_usage_by_activity($this->quba); } if (!$stepdata) { return; } $dm->insert_all_step_data(call_user_func_array('array_merge', $stepdata)); }
/** * Deletes question and all associated data from the database * * It will not delete a question if it is used by an activity module * @param object $question The question being deleted */ function question_delete_question($questionid) { global $DB; $question = $DB->get_record_sql(' SELECT q.*, qc.contextid FROM {question} q JOIN {question_categories} qc ON qc.id = q.category WHERE q.id = ?', array($questionid)); if (!$question) { // In some situations, for example if this was a child of a // Cloze question that was previously deleted, the question may already // have gone. In this case, just do nothing. return; } // Do not delete a question if it is used by an activity module if (questions_in_use(array($questionid))) { return; } // Check permissions. question_require_capability_on($question, 'edit'); $dm = new question_engine_data_mapper(); $dm->delete_previews($questionid); // delete questiontype-specific data question_bank::get_qtype($question->qtype, false)->delete_question($questionid, $question->contextid); // Now recursively delete all child questions if ($children = $DB->get_records('question', array('parent' => $questionid), '', 'id, qtype')) { foreach ($children as $child) { if ($child->id != $questionid) { question_delete_question($child->id); } } } // Finally delete the question record itself $DB->delete_records('question', array('id' => $questionid)); question_bank::notify_question_edited($questionid); }
/** * Analyse all the response data for for all the specified attempts at this question. * * @param \qubaid_condition $qubaids which attempts to consider. * @param string $whichtries which tries to analyse. Will be one of * \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES. * @return analysis_for_question */ public function calculate($qubaids, $whichtries = \question_attempt::LAST_TRY) { // Load data. $dm = new \question_engine_data_mapper(); $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids); // Analyse it. foreach ($questionattempts as $qa) { $responseparts = $qa->classify_response($whichtries); if ($this->breakdownbyvariant) { $this->analysis->count_response_parts($qa->get_variant(), $responseparts); } else { $this->analysis->count_response_parts(1, $responseparts); } } $this->analysis->cache($qubaids, $whichtries, $this->questiondata->id); return $this->analysis; }
/** * Get a list of usage ids where the question with slot $slot, and optionally * also with question id $questionid, is in summary state $summarystate. Also * return the total count of such states. * * Only a subset of the ids can be returned by using $orderby, $limitfrom and * $limitnum. A special value 'random' can be passed as $orderby, in which case * $limitfrom is ignored. * * @param int $slot The slot for the questions you want to konw about. * @param int $questionid (optional) Only return attempts that were of this specific question. * @param string $summarystate 'all', 'needsgrading', 'autograded' or 'manuallygraded'. * @param string $orderby 'random', 'date', 'student' or 'idnumber'. * @param int $page implements paging of the results. * Ignored if $orderby = random or $pagesize is null. * @param int $pagesize implements paging of the results. null = all. */ protected function get_usage_ids_where_question_in_state($summarystate, $slot, $questionid = null, $orderby = 'random', $page = 0, $pagesize = null) { global $CFG, $DB; $dm = new question_engine_data_mapper(); if ($pagesize && $orderby != 'random') { $limitfrom = $page * $pagesize; } else { $limitfrom = 0; } $qubaids = $this->get_qubaids_condition(); $params = array(); if ($orderby == 'date') { list($statetest, $params) = $dm->in_summary_state_test( 'manuallygraded', false, 'mangrstate'); $orderby = "( SELECT MAX(sortqas.timecreated) FROM {question_attempt_steps} sortqas WHERE sortqas.questionattemptid = qa.id AND sortqas.state $statetest )"; } else if ($orderby == 'student' || $orderby == 'idnumber') { $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id "; if ($orderby == 'student') { $orderby = $DB->sql_fullname('u.firstname', 'u.lastname'); } } return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate, $slot, $questionid, $orderby, $params, $limitfrom, $pagesize); }
/** * Analyse all the response data for for all the specified attempts at * this question. * @param \qubaid_condition $qubaids which attempts to consider. * @return analysis_for_question */ public function calculate($qubaids) { // Load data. $dm = new \question_engine_data_mapper(); $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids); // Analyse it. foreach ($questionattempts as $qa) { $responseparts = $qa->classify_response(); $this->analysis->count_response_parts($responseparts); } $this->analysis->cache($qubaids, $this->questiondata->id); return $this->analysis; }