[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/question/classes/statistics/questions/ -> all_calculated_for_qubaid_condition.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1