protected function check_typical_in_query(qubaid_condition $qubaids, $expectedsql, $expectedparams) { $sql = "SELECT qa.id, qa.maxmark\n FROM {question_attempts} qa\n WHERE qa.questionusageid {$qubaids->usage_id_in()}"; // NOTE: parameter names may change thanks to $DB->inorequaluniqueindex, normal comparison is very wrong!! list($sql, $params) = $this->normalize_sql($sql, $qubaids->usage_id_in_params()); list($expectedsql, $expectedparams) = $this->normalize_sql($expectedsql, $expectedparams); $this->assertEquals($expectedsql, $sql); $this->assertEquals($expectedparams, $params); }
/** * @param \qubaid_condition $qubaids * @param int $questionid the question id * @param string $subpartid * @param string $responseclassid */ public function cache($qubaids, $questionid, $subpartid, $responseclassid) { global $DB; $row = new \stdClass(); $row->hashcode = $qubaids->get_hash_code(); $row->questionid = $questionid; $row->subqid = $subpartid; if ($responseclassid === '') { $row->aid = null; } else { $row->aid = $responseclassid; } $row->response = $this->response; $row->rcount = $this->count; $row->credit = $this->fraction; $row->timemodified = time(); $DB->insert_record('question_response_analysis', $row, false); }
/** * @param array $questionids of question ids. * @param qubaid_condition $qubaids ids of the usages to consider. * @return boolean whether any of these questions are being used by any of * those usages. */ public function questions_in_use(array $questionids, qubaid_condition $qubaids) { list($test, $params) = $this->db->get_in_or_equal($questionids); return $this->db->record_exists_select('question_attempts', 'questionid ' . $test . ' AND questionusageid ' . $qubaids->usage_id_in(), $params + $qubaids->usage_id_in_params()); }
/** * Find time of non-expired statistics in the database. * * @param \qubaid_condition $qubaids Which question usages to look for stats for? * @return int|bool Time of cached record that matches this qubaid_condition or false if non found. */ public function get_last_calculated_time($qubaids) { global $DB; $timemodified = time() - self::TIME_TO_CACHE; return $DB->get_field_select('question_statistics', 'timemodified', 'hashcode = ? AND timemodified > ?', array($qubaids->get_hash_code(), $timemodified), IGNORE_MULTIPLE); }
/** * Get the ids of all the questions in a list of categories, with the number * of times they have already been used in a given set of usages. * * The result array is returned in order of increasing (count previous uses). * * @param array $categoryids an array question_category ids. * @param qubaid_condition $qubaids which question_usages to count previous uses from. * @param string $extraconditions extra conditions to AND with the rest of * the where clause. Must use named parameters. * @param array $extraparams any parameters used by $extraconditions. * @return array questionid => count of number of previous uses. */ public function get_questions_from_categories_with_usage_counts($categoryids, qubaid_condition $qubaids, $extraconditions = '', $extraparams = array()) { global $DB; list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc'); if ($extraconditions) { $extraconditions = ' AND (' . $extraconditions . ')'; } return $DB->get_records_sql_menu("\n SELECT q.id, (SELECT COUNT(1)\n FROM " . $qubaids->from_question_attempts('qa') . "\n WHERE qa.questionid = q.id AND " . $qubaids->where() . "\n ) AS previous_attempts\n\n FROM {question} q\n\n WHERE q.category {$qcsql} {$extraconditions}\n\n ORDER BY previous_attempts\n ", $qubaids->from_where_params() + $qcparams + $extraparams); }
/** * Find time of non-expired analysis in the database. * * @param \qubaid_condition $qubaids check for the analysis of which question usages? * @param string $whichtries check for the analysis of which tries? * @return integer|boolean Time of cached record that matches this qubaid_condition or false if none found. */ public function get_last_analysed_time($qubaids, $whichtries) { global $DB; $timemodified = time() - self::TIME_TO_CACHE; return $DB->get_field_select('question_response_analysis', 'timemodified', 'hashcode = ? AND whichtries = ? AND questionid = ? AND timemodified > ?', array($qubaids->get_hash_code(), $whichtries, $this->questiondata->id, $timemodified), IGNORE_MULTIPLE); }
/** * Get all the STACK questions used in all the attempts at a quiz. (Note that * Moodle random questions may be being used.) * @param qubaid_condition $qubaids the attempts of interest. * @return array of rows from the question table. */ protected function get_stack_questions_used_in_attempt(qubaid_condition $qubaids) { global $DB; return $DB->get_records_sql("\n SELECT q.*\n FROM {question} q\n JOIN (\n SELECT qa.questionid, MIN(qa.slot) AS firstslot\n FROM {$qubaids->from_question_attempts('qa')}\n WHERE {$qubaids->where()}\n GROUP BY qa.questionid\n ) usedquestionids ON q.id = usedquestionids.questionid\n WHERE q.qtype = 'stack'\n ORDER BY usedquestionids.firstslot\n ", $qubaids->from_where_params()); }
/** * Get the number of times each variant has been used for each question in a list * in a set of usages. * @param array $questionids of question ids. * @param qubaid_condition $qubaids ids of the usages to consider. * @return array questionid => variant number => num uses. */ public function load_used_variants(array $questionids, qubaid_condition $qubaids) { list($test, $params) = $this->db->get_in_or_equal($questionids, SQL_PARAMS_NAMED, 'qid'); $recordset = $this->db->get_recordset_sql("\n SELECT qa.questionid, qa.variant, COUNT(1) AS usescount\n FROM " . $qubaids->from_question_attempts('qa') . "\n WHERE qa.questionid {$test}\n AND " . $qubaids->where() . "\n GROUP BY qa.questionid, qa.variant\n ORDER BY COUNT(1) ASC\n ", $params + $qubaids->from_where_params()); $usedvariants = array_combine($questionids, array_fill(0, count($questionids), array())); foreach ($recordset as $row) { $usedvariants[$row->questionid][$row->variant] = $row->usescount; } $recordset->close(); return $usedvariants; }
/** * Cache calculated stats stored in this object in 'question_statistics' table. * * @param \qubaid_condition $qubaids */ public function cache($qubaids) { global $DB; $toinsert = new \stdClass(); $toinsert->hashcode = $qubaids->get_hash_code(); $toinsert->timemodified = time(); foreach ($this->fieldsindb as $field) { $toinsert->{$field} = $this->{$field}; } $DB->insert_record('question_statistics', $toinsert, false); }
/** * Output the HTML needed to show the statistics graph. * * @param int|object $quizorid The quiz, or its ID. * @param qubaid_condition $qubaids the question usages whose responses to analyse. * @param string $whichattempts Which attempts constant. */ protected function output_statistics_graph($quizorid, $qubaids) { global $DB, $PAGE; $quiz = $quizorid; if (!is_object($quiz)) { $quiz = $DB->get_record('quiz', array('id' => $quizorid), '*', MUST_EXIST); } // Load the rest of the required data. $questions = quiz_report_get_significant_questions($quiz); // Only load main question not sub questions. $questionstatistics = $DB->get_records_select('question_statistics', 'hashcode = ? AND slot IS NOT NULL', [$qubaids->get_hash_code()]); // Configure what to display. $fieldstoplot = ['facility' => get_string('facility', 'quiz_statistics'), 'discriminativeefficiency' => get_string('discriminative_efficiency', 'quiz_statistics')]; $fieldstoplotfactor = ['facility' => 100, 'discriminativeefficiency' => 1]; // Prepare the arrays to hold the data. $xdata = []; foreach (array_keys($fieldstoplot) as $fieldtoplot) { $ydata[$fieldtoplot] = []; } // Fill in the data for each question. foreach ($questionstatistics as $questionstatistic) { $number = $questions[$questionstatistic->slot]->number; $xdata[$number] = $number; foreach ($fieldstoplot as $fieldtoplot => $notused) { $value = $questionstatistic->{$fieldtoplot}; if (is_null($value)) { $value = 0; } $value *= $fieldstoplotfactor[$fieldtoplot]; $ydata[$fieldtoplot][$number] = number_format($value, 2); } } // Create the chart. sort($xdata); $chart = new \core\chart_bar(); $chart->get_xaxis(0, true)->set_label(get_string('position', 'quiz_statistics')); $chart->set_labels(array_values($xdata)); foreach ($fieldstoplot as $fieldtoplot => $notused) { ksort($ydata[$fieldtoplot]); $series = new \core\chart_series($fieldstoplot[$fieldtoplot], array_values($ydata[$fieldtoplot])); $chart->add_series($series); } // Find max. $max = 0; foreach ($fieldstoplot as $fieldtoplot => $notused) { $max = max($max, max($ydata[$fieldtoplot])); } // Set Y properties. $yaxis = $chart->get_yaxis(0, true); $yaxis->set_stepsize(10); $yaxis->set_label('%'); $output = $PAGE->get_renderer('mod_quiz'); $graphname = get_string('statisticsreportgraph', 'quiz_statistics'); echo $output->chart($chart, $graphname); }
/** * Cache analysis for class. * * @param \qubaid_condition $qubaids which question usages have been analysed. * @param string $whichtries which tries have been analysed? * @param int $questionid which question. * @param int $variantno which variant. * @param string $subpartid which sub part is this actual response in? * @param string $responseclassid which response class is this actual response in? */ public function cache($qubaids, $whichtries, $questionid, $variantno, $subpartid, $responseclassid) { global $DB; $row = new \stdClass(); $row->hashcode = $qubaids->get_hash_code(); $row->whichtries = $whichtries; $row->questionid = $questionid; $row->variant = $variantno; $row->subqid = $subpartid; if ($responseclassid === '') { $row->aid = null; } else { $row->aid = $responseclassid; } $row->response = $this->response; $row->credit = $this->fraction; $row->timemodified = time(); $analysisid = $DB->insert_record('question_response_analysis', $row); if ($whichtries === \question_attempt::ALL_TRIES) { foreach ($this->trycount as $try => $count) { $countrow = new \stdClass(); $countrow->try = $try; $countrow->rcount = $count; $countrow->analysisid = $analysisid; $DB->insert_record('question_response_count', $countrow, false); } } else { $countrow = new \stdClass(); $countrow->try = 0; $countrow->rcount = $this->totalcount; $countrow->analysisid = $analysisid; $DB->insert_record('question_response_count', $countrow, false); } }
/** * Retrieve the computed response analysis from the question_response_analysis table. * * @param \qubaid_condition $qubaids which attempts to get cached response analysis for. * @return analysis_for_question|boolean analysis or false if no cached analysis found. */ public function load_cached($qubaids) { global $DB; $timemodified = time() - self::TIME_TO_CACHE; $rows = $DB->get_records_select('question_response_analysis', 'hashcode = ? AND questionid = ? AND timemodified > ?', array($qubaids->get_hash_code(), $this->questiondata->id, $timemodified)); if (!$rows) { return false; } foreach ($rows as $row) { $class = $this->analysis->get_subpart($row->subqid)->get_response_class($row->aid); $class->add_response_and_count($row->response, $row->credit, $row->rcount); } return $this->analysis; }
protected function check_typical_in_query(qubaid_condition $qubaids, $expectedsql, $expectedparams) { $sql = "SELECT qa.id, qa.maxmark\n FROM {question_attempts} qa\n WHERE qa.questionusageid {$qubaids->usage_id_in()}"; $this->assertEqual($expectedsql, $sql); $this->assertEqual($expectedparams, $qubaids->usage_id_in_params()); }