Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains the code to analyse all the responses to a particular question. 19 * 20 * @package core_question 21 * @copyright 2014 Open University 22 * @author Jamie Pratt <me@jamiep.org> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace core_question\statistics\responses; 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * This class can compute, store and cache the analysis of the responses to a particular question. 31 * 32 * @package core_question 33 * @copyright 2014 The Open University 34 * @author James Pratt me@jamiep.org 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class analyser { 38 /** 39 * @var int When analysing responses and breaking down the count of responses per try, how many columns should we break down 40 * tries into? This is set to 5 columns, any response in a try more than try 5 will be counted in the fifth column. 41 */ 42 const MAX_TRY_COUNTED = 5; 43 44 /** 45 * @var int previously, the time after which statistics are automatically recomputed. 46 * @deprecated since Moodle 4.3. Use of pre-computed stats is no longer time-limited. 47 * @todo MDL-78090 Final deprecation in Moodle 4.7 48 */ 49 const TIME_TO_CACHE = 900; // 15 minutes. 50 51 /** @var object full question data from db. */ 52 protected $questiondata; 53 54 /** 55 * @var analysis_for_question|analysis_for_question_all_tries 56 */ 57 public $analysis; 58 59 /** 60 * @var int used during calculations, so all results are stored with the same timestamp. 61 */ 62 protected $calculationtime; 63 64 /** 65 * @var array Two index array first index is unique string for each sub question part, the second string index is the 'class' 66 * that sub-question part can be classified into. 67 * 68 * This is the return value from {@link \question_type::get_possible_responses()} see that method for fuller documentation. 69 */ 70 public $responseclasses = array(); 71 72 /** 73 * @var bool whether to break down response analysis by variant. This only applies to questions that have variants and is 74 * used to suppress the break down of analysis by variant when there are going to be very many variants. 75 */ 76 protected $breakdownbyvariant; 77 78 /** 79 * Create a new instance of this class for holding/computing the statistics 80 * for a particular question. 81 * 82 * @param object $questiondata the full question data from the database defining this question. 83 * @param string $whichtries which tries to analyse. 84 */ 85 public function __construct($questiondata, $whichtries = \question_attempt::LAST_TRY) { 86 $this->questiondata = $questiondata; 87 $qtypeobj = \question_bank::get_qtype($this->questiondata->qtype); 88 if ($whichtries != \question_attempt::ALL_TRIES) { 89 $this->analysis = new analysis_for_question($qtypeobj->get_possible_responses($this->questiondata)); 90 } else { 91 $this->analysis = new analysis_for_question_all_tries($qtypeobj->get_possible_responses($this->questiondata)); 92 } 93 94 $this->breakdownbyvariant = $qtypeobj->break_down_stats_and_response_analysis_by_variant($this->questiondata); 95 } 96 97 /** 98 * Does the computed analysis have sub parts? 99 * 100 * @return bool whether this analysis has more than one subpart. 101 */ 102 public function has_subparts() { 103 return count($this->responseclasses) > 1; 104 } 105 106 /** 107 * Does the computed analysis's sub parts have classes? 108 * 109 * @return bool whether this analysis has (a subpart with) more than one response class. 110 */ 111 public function has_response_classes() { 112 foreach ($this->responseclasses as $partclasses) { 113 if (count($partclasses) > 1) { 114 return true; 115 } 116 } 117 return false; 118 } 119 120 /** 121 * Analyse all the response data for all the specified attempts at this question. 122 * 123 * @param \qubaid_condition $qubaids which attempts to consider. 124 * @param string $whichtries which tries to analyse. Will be one of 125 * \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES. 126 * @return analysis_for_question 127 */ 128 public function calculate($qubaids, $whichtries = \question_attempt::LAST_TRY) { 129 $this->calculationtime = time(); 130 // Load data. 131 $dm = new \question_engine_data_mapper(); 132 $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids); 133 134 // Analyse it. 135 foreach ($questionattempts as $qa) { 136 $responseparts = $qa->classify_response($whichtries); 137 if ($this->breakdownbyvariant) { 138 $this->analysis->count_response_parts($qa->get_variant(), $responseparts); 139 } else { 140 $this->analysis->count_response_parts(1, $responseparts); 141 } 142 143 } 144 $this->analysis->cache($qubaids, $whichtries, $this->questiondata->id, $this->calculationtime); 145 return $this->analysis; 146 } 147 148 /** 149 * Retrieve the computed response analysis from the question_response_analysis table. 150 * 151 * @param \qubaid_condition $qubaids load the analysis of which question usages? 152 * @param string $whichtries load the analysis of which tries? 153 * @return analysis_for_question|boolean analysis or false if no cached analysis found. 154 */ 155 public function load_cached($qubaids, $whichtries) { 156 global $DB; 157 158 $timemodified = self::get_last_analysed_time($qubaids, $whichtries); 159 // Variable name 'analyses' is the plural of 'analysis'. 160 $responseanalyses = $DB->get_records('question_response_analysis', 161 ['hashcode' => $qubaids->get_hash_code(), 'whichtries' => $whichtries, 162 'questionid' => $this->questiondata->id, 'timemodified' => $timemodified]); 163 if (!$responseanalyses) { 164 return false; 165 } 166 167 $analysisids = []; 168 foreach ($responseanalyses as $responseanalysis) { 169 $analysisforsubpart = $this->analysis->get_analysis_for_subpart($responseanalysis->variant, $responseanalysis->subqid); 170 $class = $analysisforsubpart->get_response_class($responseanalysis->aid); 171 $class->add_response($responseanalysis->response, $responseanalysis->credit); 172 $analysisids[] = $responseanalysis->id; 173 } 174 [$sql, $params] = $DB->get_in_or_equal($analysisids); 175 $counts = $DB->get_records_select('question_response_count', "analysisid {$sql}", $params); 176 foreach ($counts as $count) { 177 $responseanalysis = $responseanalyses[$count->analysisid]; 178 $analysisforsubpart = $this->analysis->get_analysis_for_subpart($responseanalysis->variant, $responseanalysis->subqid); 179 $class = $analysisforsubpart->get_response_class($responseanalysis->aid); 180 $class->set_response_count($responseanalysis->response, $count->try, $count->rcount); 181 182 } 183 return $this->analysis; 184 } 185 186 187 /** 188 * Find time of non-expired analysis in the database. 189 * 190 * @param \qubaid_condition $qubaids check for the analysis of which question usages? 191 * @param string $whichtries check for the analysis of which tries? 192 * @return integer|boolean Time of cached record that matches this qubaid_condition or false if none found. 193 */ 194 public function get_last_analysed_time($qubaids, $whichtries) { 195 global $DB; 196 return $DB->get_field('question_response_analysis', 'MAX(timemodified)', 197 ['hashcode' => $qubaids->get_hash_code(), 'whichtries' => $whichtries, 198 'questionid' => $this->questiondata->id]); 199 } 200 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body