Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 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  namespace quiz_statistics;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  /**
  22   * The statistics calculator returns an instance of this class which contains the calculated statistics.
  23   *
  24   * These quiz statistics calculations are described here :
  25   *
  26   * http://docs.moodle.org/dev/Quiz_statistics_calculations#Test_statistics
  27   *
  28   * @package    quiz_statistics
  29   * @copyright  2013 The Open University
  30   * @author     James Pratt me@jamiep.org
  31   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   */
  33  class calculated {
  34  
  35      /**
  36       * @param  string $whichattempts which attempts to use, represented internally as one of the constants as used in
  37       *                                   $quiz->grademethod ie.
  38       *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
  39       *                                   we calculate stats based on which attempts would affect the grade for each student,
  40       *                                   the default null value is used when constructing an instance whose values will be
  41       *                                   populated from a db record.
  42       */
  43      public function __construct($whichattempts = null) {
  44          if ($whichattempts !== null) {
  45              $this->whichattempts = $whichattempts;
  46          }
  47      }
  48  
  49      /**
  50       * @var int which attempts we are calculating calculate stats from.
  51       */
  52      public $whichattempts;
  53  
  54      /* Following stats all described here : http://docs.moodle.org/dev/Quiz_statistics_calculations#Test_statistics  */
  55  
  56      public $firstattemptscount = 0;
  57  
  58      public $allattemptscount = 0;
  59  
  60      public $lastattemptscount = 0;
  61  
  62      public $highestattemptscount = 0;
  63  
  64      public $firstattemptsavg;
  65  
  66      public $allattemptsavg;
  67  
  68      public $lastattemptsavg;
  69  
  70      public $highestattemptsavg;
  71  
  72      public $median;
  73  
  74      public $standarddeviation;
  75  
  76      public $skewness;
  77  
  78      public $kurtosis;
  79  
  80      public $cic;
  81  
  82      public $errorratio;
  83  
  84      public $standarderror;
  85  
  86      /**
  87       * @var int time these stats where calculated and cached.
  88       */
  89      public $timemodified;
  90  
  91      /**
  92       * Count of attempts selected by $this->whichattempts
  93       *
  94       * @return int
  95       */
  96      public function s() {
  97          return $this->get_field('count');
  98      }
  99  
 100      /**
 101       * Average grade for the attempts selected by $this->whichattempts
 102       *
 103       * @return float
 104       */
 105      public function avg() {
 106          return $this->get_field('avg');
 107      }
 108  
 109      /**
 110       * Get the right field name to fetch a stat for these attempts that is calculated for more than one $whichattempts (count or
 111       * avg).
 112       *
 113       * @param string $field name of field
 114       * @return int|float
 115       */
 116      protected function get_field($field) {
 117          $fieldname = calculator::using_attempts_string_id($this->whichattempts).$field;
 118          return $this->{$fieldname};
 119      }
 120  
 121      /**
 122       * @param $course
 123       * @param $cm
 124       * @param $quiz
 125       * @return array to display in table or spreadsheet.
 126       */
 127      public function get_formatted_quiz_info_data($course, $cm, $quiz) {
 128  
 129          // You can edit this array to control which statistics are displayed.
 130          $todisplay = ['firstattemptscount' => 'number',
 131                             'allattemptscount' => 'number',
 132                             'firstattemptsavg' => 'summarks_as_percentage',
 133                             'allattemptsavg' => 'summarks_as_percentage',
 134                             'lastattemptsavg' => 'summarks_as_percentage',
 135                             'highestattemptsavg' => 'summarks_as_percentage',
 136                             'median' => 'summarks_as_percentage',
 137                             'standarddeviation' => 'summarks_as_percentage',
 138                             'skewness' => 'number_format',
 139                             'kurtosis' => 'number_format',
 140                             'cic' => 'number_format_percent',
 141                             'errorratio' => 'number_format_percent',
 142                             'standarderror' => 'summarks_as_percentage'];
 143  
 144          // General information about the quiz.
 145          $quizinfo = [];
 146          $quizinfo[get_string('quizname', 'quiz_statistics')] = format_string($quiz->name);
 147          $quizinfo[get_string('coursename', 'quiz_statistics')] = format_string($course->fullname);
 148          if ($cm->idnumber) {
 149              $quizinfo[get_string('idnumbermod')] = $cm->idnumber;
 150          }
 151          if ($quiz->timeopen) {
 152              $quizinfo[get_string('quizopen', 'quiz')] = userdate($quiz->timeopen);
 153          }
 154          if ($quiz->timeclose) {
 155              $quizinfo[get_string('quizclose', 'quiz')] = userdate($quiz->timeclose);
 156          }
 157          if ($quiz->timeopen && $quiz->timeclose) {
 158              $quizinfo[get_string('duration', 'quiz_statistics')] =
 159                  format_time($quiz->timeclose - $quiz->timeopen);
 160          }
 161  
 162          // The statistics.
 163          foreach ($todisplay as $property => $format) {
 164              if (!isset($this->$property) || !$format) {
 165                  continue;
 166              }
 167              $value = $this->$property;
 168  
 169              switch ($format) {
 170                  case 'summarks_as_percentage':
 171                      $formattedvalue = quiz_report_scale_summarks_as_percentage($value, $quiz);
 172                      break;
 173                  case 'number_format_percent':
 174                      $formattedvalue = quiz_format_grade($quiz, $value) . '%';
 175                      break;
 176                  case 'number_format':
 177                      // 2 extra decimal places, since not a percentage,
 178                      // and we want the same number of sig figs.
 179                      $formattedvalue = format_float($value, $quiz->decimalpoints + 2);
 180                      break;
 181                  case 'number':
 182                      $formattedvalue = $value + 0;
 183                      break;
 184                  default:
 185                      $formattedvalue = $value;
 186              }
 187  
 188              $quizinfo[get_string($property, 'quiz_statistics',
 189                                   calculator::using_attempts_lang_string($this->whichattempts))] = $formattedvalue;
 190          }
 191  
 192          return $quizinfo;
 193      }
 194  
 195      /**
 196       * @var array of names of properties of this class that are cached in db record.
 197       */
 198      protected $fieldsindb = ['whichattempts', 'firstattemptscount', 'allattemptscount', 'firstattemptsavg', 'allattemptsavg',
 199                                      'lastattemptscount', 'highestattemptscount', 'lastattemptsavg', 'highestattemptsavg',
 200                                      'median', 'standarddeviation', 'skewness',
 201                                      'kurtosis', 'cic', 'errorratio', 'standarderror'];
 202  
 203      /**
 204       * Cache the stats contained in this class.
 205       *
 206       * @param $qubaids \qubaid_condition
 207       */
 208      public function cache($qubaids) {
 209          global $DB;
 210  
 211          $toinsert = new \stdClass();
 212  
 213          foreach ($this->fieldsindb as $field) {
 214              $toinsert->{$field} = $this->{$field};
 215          }
 216  
 217          $toinsert->hashcode = $qubaids->get_hash_code();
 218          $toinsert->timemodified = time();
 219  
 220          // Fix up some dodgy data.
 221          if (isset($toinsert->errorratio) && is_nan($toinsert->errorratio)) {
 222              $toinsert->errorratio = null;
 223          }
 224          if (isset($toinsert->standarderror) && is_nan($toinsert->standarderror)) {
 225              $toinsert->standarderror = null;
 226          }
 227  
 228          // Delete older statistics before we save the new ones.
 229          $transaction = $DB->start_delegated_transaction();
 230          $DB->delete_records('quiz_statistics', ['hashcode' => $qubaids->get_hash_code()]);
 231  
 232          // Store the data.
 233          $DB->insert_record('quiz_statistics', $toinsert);
 234          $transaction->allow_commit();
 235      }
 236  
 237      /**
 238       * Given a record from 'quiz_statistics' table load the data into the properties of this class.
 239       *
 240       * @param $record \stdClass from db.
 241       */
 242      public function populate_from_record($record) {
 243          foreach ($this->fieldsindb as $field) {
 244              $this->$field = $record->$field;
 245          }
 246          $this->timemodified = $record->timemodified;
 247      }
 248  }