See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 /** @var int Time after which responses are automatically reanalysed. */ 45 const TIME_TO_CACHE = 900; // 15 minutes. 46 47 /** @var object full question data from db. */ 48 protected $questiondata; 49 50 /** 51 * @var analysis_for_question|analysis_for_question_all_tries 52 */ 53 public $analysis; 54 55 /** 56 * @var array Two index array first index is unique string for each sub question part, the second string index is the 'class' 57 * that sub-question part can be classified into. 58 * 59 * This is the return value from {@link \question_type::get_possible_responses()} see that method for fuller documentation. 60 */ 61 public $responseclasses = array(); 62 63 /** 64 * @var bool whether to break down response analysis by variant. This only applies to questions that have variants and is 65 * used to suppress the break down of analysis by variant when there are going to be very many variants. 66 */ 67 protected $breakdownbyvariant; 68 69 /** 70 * Create a new instance of this class for holding/computing the statistics 71 * for a particular question. 72 * 73 * @param object $questiondata the full question data from the database defining this question. 74 * @param string $whichtries which tries to analyse. 75 */ 76 public function __construct($questiondata, $whichtries = \question_attempt::LAST_TRY) { 77 $this->questiondata = $questiondata; 78 $qtypeobj = \question_bank::get_qtype($this->questiondata->qtype); 79 if ($whichtries != \question_attempt::ALL_TRIES) { 80 $this->analysis = new analysis_for_question($qtypeobj->get_possible_responses($this->questiondata)); 81 } else { 82 $this->analysis = new analysis_for_question_all_tries($qtypeobj->get_possible_responses($this->questiondata)); 83 } 84 85 $this->breakdownbyvariant = $qtypeobj->break_down_stats_and_response_analysis_by_variant($this->questiondata); 86 } 87 88 /** 89 * Does the computed analysis have sub parts? 90 * 91 * @return bool whether this analysis has more than one subpart. 92 */ 93 public function has_subparts() { 94 return count($this->responseclasses) > 1; 95 } 96 97 /** 98 * Does the computed analysis's sub parts have classes? 99 * 100 * @return bool whether this analysis has (a subpart with) more than one response class. 101 */ 102 public function has_response_classes() { 103 foreach ($this->responseclasses as $partclasses) { 104 if (count($partclasses) > 1) { 105 return true; 106 } 107 } 108 return false; 109 } 110 111 /** 112 * Analyse all the response data for for all the specified attempts at this question. 113 * 114 * @param \qubaid_condition $qubaids which attempts to consider. 115 * @param string $whichtries which tries to analyse. Will be one of 116 * \question_attempt::FIRST_TRY, LAST_TRY or ALL_TRIES. 117 * @return analysis_for_question 118 */ 119 public function calculate($qubaids, $whichtries = \question_attempt::LAST_TRY) { 120 // Load data. 121 $dm = new \question_engine_data_mapper(); 122 $questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids); 123 124 // Analyse it. 125 foreach ($questionattempts as $qa) { 126 $responseparts = $qa->classify_response($whichtries); 127 if ($this->breakdownbyvariant) { 128 $this->analysis->count_response_parts($qa->get_variant(), $responseparts); 129 } else { 130 $this->analysis->count_response_parts(1, $responseparts); 131 } 132 133 } 134 $this->analysis->cache($qubaids, $whichtries, $this->questiondata->id); 135 return $this->analysis; 136 } 137 138 /** 139 * Retrieve the computed response analysis from the question_response_analysis table. 140 * 141 * @param \qubaid_condition $qubaids load the analysis of which question usages? 142 * @param string $whichtries load the analysis of which tries? 143 * @return analysis_for_question|boolean analysis or false if no cached analysis found. 144 */ 145 public function load_cached($qubaids, $whichtries) { 146 global $DB; 147 148 $timemodified = time() - self::TIME_TO_CACHE; 149 // Variable name 'analyses' is the plural of 'analysis'. 150 $responseanalyses = $DB->get_records_select('question_response_analysis', 151 'hashcode = ? AND whichtries = ? AND questionid = ? AND timemodified > ?', 152 array($qubaids->get_hash_code(), $whichtries, $this->questiondata->id, $timemodified)); 153 if (!$responseanalyses) { 154 return false; 155 } 156 157 $analysisids = array(); 158 foreach ($responseanalyses as $responseanalysis) { 159 $analysisforsubpart = $this->analysis->get_analysis_for_subpart($responseanalysis->variant, $responseanalysis->subqid); 160 $class = $analysisforsubpart->get_response_class($responseanalysis->aid); 161 $class->add_response($responseanalysis->response, $responseanalysis->credit); 162 $analysisids[] = $responseanalysis->id; 163 } 164 list($sql, $params) = $DB->get_in_or_equal($analysisids); 165 $counts = $DB->get_records_select('question_response_count', "analysisid {$sql}", $params); 166 foreach ($counts as $count) { 167 $responseanalysis = $responseanalyses[$count->analysisid]; 168 $analysisforsubpart = $this->analysis->get_analysis_for_subpart($responseanalysis->variant, $responseanalysis->subqid); 169 $class = $analysisforsubpart->get_response_class($responseanalysis->aid); 170 $class->set_response_count($responseanalysis->response, $count->try, $count->rcount); 171 172 } 173 return $this->analysis; 174 } 175 176 177 /** 178 * Find time of non-expired analysis in the database. 179 * 180 * @param \qubaid_condition $qubaids check for the analysis of which question usages? 181 * @param string $whichtries check for the analysis of which tries? 182 * @return integer|boolean Time of cached record that matches this qubaid_condition or false if none found. 183 */ 184 public function get_last_analysed_time($qubaids, $whichtries) { 185 global $DB; 186 187 $timemodified = time() - self::TIME_TO_CACHE; 188 return $DB->get_field_select('question_response_analysis', 'timemodified', 189 'hashcode = ? AND whichtries = ? AND questionid = ? AND timemodified > ?', 190 array($qubaids->get_hash_code(), $whichtries, $this->questiondata->id, $timemodified), 191 IGNORE_MULTIPLE); 192 } 193 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body