[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * A collection of all the question statistics calculated for an activity instance ie. the stats calculated for slots and 19 * sub-questions and variants of those questions. 20 * 21 * @package core_question 22 * @copyright 2014 The Open University 23 * @author James Pratt me@jamiep.org 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 namespace core_question\statistics\questions; 28 29 /** 30 * A collection of all the question statistics calculated for an activity instance. 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 all_calculated_for_qubaid_condition { 38 39 /** @var int Time after which statistics are automatically recomputed. */ 40 const TIME_TO_CACHE = 900; // 15 minutes. 41 42 /** 43 * The limit of rows of sub-question and variants rows to display on main page of report before switching to showing min, 44 * median and max variants. 45 */ 46 const SUBQ_AND_VARIANT_ROW_LIMIT = 10; 47 48 /** 49 * @var object[] 50 */ 51 public $subquestions; 52 53 /** 54 * Holds slot (position) stats and stats for variants of questions in slots. 55 * 56 * @var calculated[] 57 */ 58 public $questionstats = array(); 59 60 /** 61 * Holds sub-question stats and stats for variants of subqs. 62 * 63 * @var calculated_for_subquestion[] 64 */ 65 public $subquestionstats = array(); 66 67 /** 68 * Set up a calculated_for_subquestion instance ready to store a randomly selected question's stats. 69 * 70 * @param object $step 71 * @param int|null $variant Is this to keep track of a variant's stats? If so what is the variant, if not null. 72 */ 73 public function initialise_for_subq($step, $variant = null) { 74 $newsubqstat = new calculated_for_subquestion($step, $variant); 75 if ($variant === null) { 76 $this->subquestionstats[$step->questionid] = $newsubqstat; 77 } else { 78 $this->subquestionstats[$step->questionid]->variantstats[$variant] = $newsubqstat; 79 } 80 } 81 82 /** 83 * Set up a calculated instance ready to store a slot question's stats. 84 * 85 * @param int $slot 86 * @param object $question 87 * @param int|null $variant Is this to keep track of a variant's stats? If so what is the variant, if not null. 88 */ 89 public function initialise_for_slot($slot, $question, $variant = null) { 90 $newqstat = new calculated($question, $slot, $variant); 91 if ($variant === null) { 92 $this->questionstats[$slot] = $newqstat; 93 } else { 94 $this->questionstats[$slot]->variantstats[$variant] = $newqstat; 95 } 96 } 97 98 /** 99 * Reference for a item stats instance for a questionid and optional variant no. 100 * 101 * @param int $questionid The id of the sub question. 102 * @param int|null $variant if not null then we want the object to store a variant of a sub-question's stats. 103 * @return calculated_for_subquestion|null null if the stats object does not yet exist. 104 */ 105 public function for_subq($questionid, $variant = null) { 106 if ($variant === null) { 107 if (!isset($this->subquestionstats[$questionid])) { 108 return null; 109 } else { 110 return $this->subquestionstats[$questionid]; 111 } 112 } else { 113 if (!isset($this->subquestionstats[$questionid]->variantstats[$variant])) { 114 return null; 115 } else { 116 return $this->subquestionstats[$questionid]->variantstats[$variant]; 117 } 118 } 119 } 120 121 /** 122 * ids of all randomly selected question for all slots. 123 * 124 * @return int[] An array of all sub-question ids. 125 */ 126 public function get_all_subq_ids() { 127 return array_keys($this->subquestionstats); 128 } 129 130 /** 131 * All slots nos that stats have been calculated for. 132 * 133 * @return int[] An array of all slot nos. 134 */ 135 public function get_all_slots() { 136 return array_keys($this->questionstats); 137 } 138 139 /** 140 * Get position stats instance for a slot and optional variant no. 141 * 142 * @param int $slot The slot no. 143 * @param null $variant if provided then we want the object which stores a variant of a position's stats. 144 * @return calculated|null An instance of the class storing the calculated position stats. 145 */ 146 public function for_slot($slot, $variant = null) { 147 if ($variant === null) { 148 if (!isset($this->questionstats[$slot])) { 149 return null; 150 } else { 151 return $this->questionstats[$slot]; 152 } 153 } else { 154 if (!isset($this->questionstats[$slot]->variantstats[$variant])) { 155 return null; 156 } else { 157 return $this->questionstats[$slot]->variantstats[$variant]; 158 } 159 } 160 } 161 162 /** 163 * Load cached statistics from the database. 164 * 165 * @param \qubaid_condition $qubaids Which question usages to load stats for? 166 */ 167 public function get_cached($qubaids) { 168 global $DB; 169 170 $timemodified = time() - self::TIME_TO_CACHE; 171 $questionstatrecs = $DB->get_records_select('question_statistics', 'hashcode = ? AND timemodified > ?', 172 array($qubaids->get_hash_code(), $timemodified)); 173 174 $questionids = array(); 175 foreach ($questionstatrecs as $fromdb) { 176 if (is_null($fromdb->variant) && !$fromdb->slot) { 177 $questionids[] = $fromdb->questionid; 178 } 179 } 180 $this->subquestions = question_load_questions($questionids); 181 foreach ($questionstatrecs as $fromdb) { 182 if (is_null($fromdb->variant)) { 183 if ($fromdb->slot) { 184 $this->questionstats[$fromdb->slot]->populate_from_record($fromdb); 185 // Array created in constructor and populated from question. 186 } else { 187 $this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion(); 188 $this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb); 189 $this->subquestionstats[$fromdb->questionid]->question = $this->subquestions[$fromdb->questionid]; 190 } 191 } 192 } 193 // Add cached variant stats to data structure. 194 foreach ($questionstatrecs as $fromdb) { 195 if (!is_null($fromdb->variant)) { 196 if ($fromdb->slot) { 197 $newcalcinstance = new calculated(); 198 $this->questionstats[$fromdb->slot]->variantstats[$fromdb->variant] = $newcalcinstance; 199 $newcalcinstance->question = $this->questionstats[$fromdb->slot]->question; 200 } else { 201 $newcalcinstance = new calculated_for_subquestion(); 202 $this->subquestionstats[$fromdb->questionid]->variantstats[$fromdb->variant] = $newcalcinstance; 203 $newcalcinstance->question = $this->subquestions[$fromdb->questionid]; 204 } 205 $newcalcinstance->populate_from_record($fromdb); 206 } 207 } 208 } 209 210 /** 211 * Find time of non-expired statistics in the database. 212 * 213 * @param \qubaid_condition $qubaids Which question usages to look for stats for? 214 * @return int|bool Time of cached record that matches this qubaid_condition or false if non found. 215 */ 216 public function get_last_calculated_time($qubaids) { 217 global $DB; 218 219 $timemodified = time() - self::TIME_TO_CACHE; 220 return $DB->get_field_select('question_statistics', 'timemodified', 'hashcode = ? AND timemodified > ?', 221 array($qubaids->get_hash_code(), $timemodified), IGNORE_MULTIPLE); 222 } 223 224 /** 225 * Save stats to db. 226 * 227 * @param \qubaid_condition $qubaids Which question usages are we caching the stats of? 228 */ 229 public function cache($qubaids) { 230 foreach ($this->get_all_slots() as $slot) { 231 $this->for_slot($slot)->cache($qubaids); 232 } 233 234 foreach ($this->get_all_subq_ids() as $subqid) { 235 $this->for_subq($subqid)->cache($qubaids); 236 } 237 } 238 239 /** 240 * Return all sub-questions used. 241 * 242 * @return \object[] array of questions. 243 */ 244 public function get_sub_questions() { 245 return $this->subquestions; 246 } 247 248 /** 249 * Return all stats for one slot, stats for the slot itself, and either : 250 * - variants of question 251 * - variants of randomly selected questions 252 * - randomly selected questions 253 * 254 * @param int $slot the slot no 255 * @param bool|int $limitvariants limit number of variants and sub-questions displayed? 256 * @return calculated|calculated_for_subquestion[] stats to display 257 */ 258 public function structure_analysis_for_one_slot($slot, $limitvariants = false) { 259 return array_merge(array($this->for_slot($slot)), $this->all_subq_and_variant_stats_for_slot($slot, $limitvariants)); 260 } 261 262 /** 263 * Call after calculations to output any error messages. 264 * 265 * @return string[] Array of strings describing error messages found during stats calculation. 266 */ 267 public function any_error_messages() { 268 $errors = array(); 269 foreach ($this->get_all_slots() as $slot) { 270 foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) { 271 if ($this->for_subq($subqid)->differentweights) { 272 $name = $this->for_subq($subqid)->question->name; 273 $errors[] = get_string('erroritemappearsmorethanoncewithdifferentweight', 'question', $name); 274 } 275 } 276 } 277 return $errors; 278 } 279 280 /** 281 * Are there too many rows of sub-questions and / or variant rows. 282 * 283 * @param array $rows the rows we intend to add. 284 * @return bool Are there too many? 285 */ 286 protected function too_many_subq_and_or_variant_rows($rows) { 287 return (count($rows) > static::SUBQ_AND_VARIANT_ROW_LIMIT); 288 } 289 290 /** 291 * From a number of calculated instances find the three instances with min, median and maximum facility index values. 292 * 293 * @param calculated[] $questionstats The stats from which to find the ones with minimum, median and maximum facility index. 294 * @return calculated[] 3 stat objects with minimum, median and maximum facility index. 295 */ 296 protected function find_min_median_and_max_facility_stats_objects($questionstats) { 297 $facilities = array(); 298 foreach ($questionstats as $key => $questionstat) { 299 $facilities[$key] = (float)$questionstat->facility; 300 } 301 asort($facilities); 302 $facilitykeys = array_keys($facilities); 303 $keyformin = $facilitykeys[0]; 304 $keyformedian = $facilitykeys[(int)(round(count($facilitykeys) / 2) - 1)]; 305 $keyformax = $facilitykeys[count($facilitykeys) - 1]; 306 $toreturn = array(); 307 foreach (array($keyformin => 'minimumfacility', 308 $keyformedian => 'medianfacility', 309 $keyformax => 'maximumfacility') as $key => $stringid) { 310 $questionstats[$key]->minmedianmaxnotice = $stringid; 311 $toreturn[] = $questionstats[$key]; 312 } 313 return $toreturn; 314 } 315 316 /** 317 * Return all stats for variants of question in slot $slot. 318 * 319 * @param int $slot The slot no. 320 * @return calculated[] The instances storing the calculated stats. 321 */ 322 protected function all_variant_stats_for_one_slot($slot) { 323 $toreturn = array(); 324 foreach ($this->for_slot($slot)->get_variants() as $variant) { 325 $toreturn[] = $this->for_slot($slot, $variant); 326 } 327 return $toreturn; 328 } 329 330 /** 331 * Return all stats for variants of randomly selected questions for one slot $slot. 332 * 333 * @param int $slot The slot no. 334 * @return calculated[] The instances storing the calculated stats. 335 */ 336 protected function all_subq_variants_for_one_slot($slot) { 337 $toreturn = array(); 338 $displayorder = 1; 339 foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) { 340 if ($variants = $this->for_subq($subqid)->get_variants()) { 341 foreach ($variants as $variant) { 342 $toreturn[] = $this->make_new_subq_stat_for($displayorder, $slot, $subqid, $variant); 343 } 344 } 345 $displayorder++; 346 } 347 return $toreturn; 348 } 349 350 /** 351 * Return all stats for randomly selected questions for one slot $slot. 352 * 353 * @param int $slot The slot no. 354 * @return calculated[] The instances storing the calculated stats. 355 */ 356 protected function all_subqs_for_one_slot($slot) { 357 $displayorder = 1; 358 $toreturn = array(); 359 foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) { 360 $toreturn[] = $this->make_new_subq_stat_for($displayorder, $slot, $subqid); 361 $displayorder++; 362 } 363 return $toreturn; 364 } 365 366 /** 367 * Return all variant or 'sub-question' stats one slot, either : 368 * - variants of question 369 * - variants of randomly selected questions 370 * - randomly selected questions 371 * 372 * @param int $slot the slot no 373 * @param bool $limited limit number of variants and sub-questions displayed? 374 * @return calculated|calculated_for_subquestion[] stats to display 375 */ 376 protected function all_subq_and_variant_stats_for_slot($slot, $limited) { 377 // Random question in this slot? 378 if ($this->for_slot($slot)->get_sub_question_ids()) { 379 if ($limited) { 380 $subqvariantstats = $this->all_subq_variants_for_one_slot($slot); 381 if ($this->too_many_subq_and_or_variant_rows($subqvariantstats)) { 382 // Too many variants from randomly selected questions. 383 return $this->find_min_median_and_max_facility_stats_objects($subqvariantstats); 384 } 385 $subqstats = $this->all_subqs_for_one_slot($slot); 386 if ($this->too_many_subq_and_or_variant_rows($subqstats)) { 387 // Too many randomly selected questions. 388 return $this->find_min_median_and_max_facility_stats_objects($subqstats); 389 } 390 } 391 $toreturn = array(); 392 $displaynumber = 1; 393 foreach ($this->for_slot($slot)->get_sub_question_ids() as $subqid) { 394 $toreturn[] = $this->make_new_subq_stat_for($displaynumber, $slot, $subqid); 395 if ($variants = $this->for_subq($subqid)->get_variants()) { 396 foreach ($variants as $variant) { 397 $toreturn[] = $this->make_new_subq_stat_for($displaynumber, $slot, $subqid, $variant); 398 } 399 } 400 $displaynumber++; 401 } 402 return $toreturn; 403 } else { 404 $variantstats = $this->all_variant_stats_for_one_slot($slot); 405 if ($limited && $this->too_many_subq_and_or_variant_rows($variantstats)) { 406 return $this->find_min_median_and_max_facility_stats_objects($variantstats); 407 } else { 408 return $variantstats; 409 } 410 } 411 } 412 413 /** 414 * We need a new object for display. Sub-question stats can appear more than once in different slots. 415 * So we create a clone of the object and then we can set properties on the object that are per slot. 416 * 417 * @param int $displaynumber The display number for this sub question. 418 * @param int $slot The slot number. 419 * @param int $subqid The sub question id. 420 * @param null|int $variant The variant no. 421 * @return calculated_for_subquestion The object for display. 422 */ 423 protected function make_new_subq_stat_for($displaynumber, $slot, $subqid, $variant = null) { 424 $slotstat = fullclone($this->for_subq($subqid, $variant)); 425 $slotstat->question->number = $this->for_slot($slot)->question->number; 426 $slotstat->subqdisplayorder = $displaynumber; 427 return $slotstat; 428 } 429 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |