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 19 * question. 20 * 21 * @package core_question 22 * @copyright 2013 Open University 23 * @author Jamie Pratt <me@jamiep.org> 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27namespace core_question\statistics\responses; 28defined('MOODLE_INTERNAL') || die(); 29 30/** 31 * Analysis for possible responses for parts of a question. It is up to a question type designer to decide on how many parts their 32 * question has. See {@link \question_type::get_possible_responses()} and sub classes where the sub parts and response classes are 33 * defined. 34 * 35 * A sub part might represent a sub question embedded in the question for example in a matching question there are 36 * several sub parts. A numeric question with a unit might be divided into two sub parts for the purposes of response analysis 37 * or the question type designer might decide to treat the answer, both the numeric and unit part, 38 * as a whole for the purposes of response analysis. 39 * 40 * - There is a separate data structure for each question or sub question's analysis 41 * {@link \core_question\statistics\responses\analysis_for_question} 42 * or {@link \core_question\statistics\responses\analysis_for_question_all_tries}. 43 * - There are separate analysis for each variant in this top level instance. 44 * - Then there are class instances representing the analysis of each of the sub parts of each variant of the question. 45 * {@link \core_question\statistics\responses\analysis_for_subpart}. 46 * - Then within the sub part analysis there are response class analysis 47 * {@link \core_question\statistics\responses\analysis_for_class}. 48 * - Then within each class analysis there are analysis for each actual response 49 * {@link \core_question\statistics\responses\analysis_for_actual_response}. 50 * 51 * @package core_question 52 * @copyright 2014 The Open University 53 * @author James Pratt me@jamiep.org 54 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 55 */ 56class analysis_for_question { 57 58 /** 59 * Constructor method. 60 * 61 * @param array[] Two index array, first index is unique string for each sub question part, 62 * the second string index is the 'class' that sub-question part can be classified into. 63 * Value in array is instance of {@link \question_possible_response} 64 * This is the return value from {@link \question_type::get_possible_responses()} 65 * see that method for fuller documentation. 66 */ 67 public function __construct(array $possiblereponses = null) { 68 if ($possiblereponses !== null) { 69 $this->possibleresponses = $possiblereponses; 70 } 71 } 72 73 /** 74 * @var array[] See description above in constructor method. 75 */ 76 protected $possibleresponses = array(); 77 78 /** 79 * A multidimensional array whose first index is variant no and second index is subpart id, array contents are of type 80 * {@link analysis_for_subpart}. 81 * 82 * @var array[] 83 */ 84 protected $subparts = array(); 85 86 /** 87 * Initialise data structure for response analysis of one variant. 88 * 89 * @param int $variantno 90 */ 91 protected function initialise_stats_for_variant($variantno) { 92 $this->subparts[$variantno] = array(); 93 foreach ($this->possibleresponses as $subpartid => $classes) { 94 $this->subparts[$variantno][$subpartid] = new analysis_for_subpart($classes); 95 } 96 } 97 98 /** 99 * Variant nos found in this question's attempt data. 100 * 101 * @return int[] 102 */ 103 public function get_variant_nos() { 104 return array_keys($this->subparts); 105 } 106 107 /** 108 * Unique ids for sub parts. 109 * 110 * @param int $variantno 111 * @return string[] 112 */ 113 public function get_subpart_ids($variantno) { 114 return array_keys($this->subparts[$variantno]); 115 } 116 117 /** 118 * Get the response counts etc. for variant $variantno, question sub part $subpartid. 119 * 120 * Or if there is no recorded analysis yet then initialise the data structure for that part of the analysis and return the 121 * initialised analysis objects. 122 * 123 * @param int $variantno 124 * @param string $subpartid id for sub part. 125 * @return analysis_for_subpart 126 */ 127 public function get_analysis_for_subpart($variantno, $subpartid) { 128 if (!isset($this->subparts[$variantno])) { 129 $this->initialise_stats_for_variant($variantno); 130 } 131 if (!isset($this->subparts[$variantno][$subpartid])) { 132 debugging('Unexpected sub-part id ' . $subpartid . 133 ' encountered.'); 134 $this->subparts[$variantno][$subpartid] = new analysis_for_subpart(); 135 } 136 return $this->subparts[$variantno][$subpartid]; 137 } 138 139 /** 140 * Used to work out what kind of table is needed to display stats. 141 * 142 * @return bool whether this question has (a subpart with) more than one response class. 143 */ 144 public function has_multiple_response_classes() { 145 foreach ($this->get_variant_nos() as $variantno) { 146 foreach ($this->get_subpart_ids($variantno) as $subpartid) { 147 if ($this->get_analysis_for_subpart($variantno, $subpartid)->has_multiple_response_classes()) { 148 return true; 149 } 150 } 151 } 152 return false; 153 } 154 155 /** 156 * Used to work out what kind of table is needed to display stats. 157 * 158 * @return bool whether this analysis has more than one subpart. 159 */ 160 public function has_subparts() { 161 foreach ($this->get_variant_nos() as $variantno) { 162 if (count($this->get_subpart_ids($variantno)) > 1) { 163 return true; 164 } 165 } 166 return false; 167 } 168 169 /** 170 * @return bool Does this response analysis include counts for responses for multiple tries of the question? 171 */ 172 public function has_multiple_tries_data() { 173 return false; 174 } 175 176 /** 177 * What is the highest number of tries at this question? 178 * 179 * @return int always 1 as this class is for analysing only one try. 180 */ 181 public function get_maximum_tries() { 182 return 1; 183 } 184 185 186 /** 187 * Takes an array of {@link \question_classified_response} and adds counts of the responses to the sub parts and classes. 188 * 189 * @param int $variantno 190 * @param \question_classified_response[] $responseparts keys are sub-part id. 191 */ 192 public function count_response_parts($variantno, $responseparts) { 193 foreach ($responseparts as $subpartid => $responsepart) { 194 $this->get_analysis_for_subpart($variantno, $subpartid)->count_response($responsepart); 195 } 196 } 197 198 /** 199 * @param \qubaid_condition $qubaids which question usages have been analysed. 200 * @param string $whichtries which tries have been analysed? 201 * @param int $questionid which question. 202 */ 203 public function cache($qubaids, $whichtries, $questionid) { 204 foreach ($this->get_variant_nos() as $variantno) { 205 foreach ($this->get_subpart_ids($variantno) as $subpartid) { 206 $analysisforsubpart = $this->get_analysis_for_subpart($variantno, $subpartid); 207 $analysisforsubpart->cache($qubaids, $whichtries, $questionid, $variantno, $subpartid); 208 } 209 } 210 } 211 212 /** 213 * @return bool whether this analysis has a response class with more than one 214 * different actual response, or if the actual response is different from 215 * the model response. 216 */ 217 public function has_actual_responses() { 218 foreach ($this->get_variant_nos() as $variantno) { 219 foreach ($this->get_subpart_ids($variantno) as $subpartid) { 220 if ($this->get_analysis_for_subpart($variantno, $subpartid)->has_actual_responses()) { 221 return true; 222 } 223 } 224 } 225 return false; 226 } 227 228} 229