Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
   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   * Abstract base indicator.
  19   *
  20   * @package   core_analytics
  21   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_analytics\local\indicator;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Abstract base indicator.
  31   *
  32   * @package   core_analytics
  33   * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  abstract class base extends \core_analytics\calculable {
  37  
  38      /**
  39       * Min value an indicator can return.
  40       */
  41      const MIN_VALUE = -1;
  42  
  43      /**
  44       * Max value an indicator can return.
  45       */
  46      const MAX_VALUE = 1;
  47  
  48      /**
  49       * Converts the calculated indicators to dataset feature/s.
  50       *
  51       * @param float|int[] $calculatedvalues
  52       * @return array
  53       */
  54      abstract protected function to_features($calculatedvalues);
  55  
  56      /**
  57       * Calculates the sample.
  58       *
  59       * Return a value from self::MIN_VALUE to self::MAX_VALUE or null if the indicator can not be calculated for this sample.
  60       *
  61       * @param int $sampleid
  62       * @param string $sampleorigin
  63       * @param integer $starttime Limit the calculation to this timestart
  64       * @param integer $endtime Limit the calculation to this timeend
  65       * @return float|null
  66       */
  67      abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime);
  68  
  69      /**
  70       * Should this value be displayed?
  71       *
  72       * Indicators providing multiple features can be used this method to discard some of them.
  73       *
  74       * @param float $value
  75       * @param string $subtype
  76       * @return bool
  77       */
  78      public function should_be_displayed($value, $subtype) {
  79          // We should everything by default.
  80          return true;
  81      }
  82  
  83      /**
  84       * Allows indicators to specify data they need.
  85       *
  86       * e.g. A model using courses as samples will not provide users data, but an indicator like
  87       * "user is hungry" needs user data.
  88       *
  89       * @return null|string[] Name of the required elements (use the database tablename)
  90       */
  91      public static function required_sample_data() {
  92          return null;
  93      }
  94  
  95      /**
  96       * Returns an instance of the indicator.
  97       *
  98       * Useful to reset cached data.
  99       *
 100       * @return \core_analytics\local\indicator\base
 101       */
 102      public static function instance() {
 103          return new static();
 104      }
 105  
 106      /**
 107       * Returns the maximum value an indicator calculation can return.
 108       *
 109       * @return float
 110       */
 111      public static function get_max_value() {
 112          return self::MAX_VALUE;
 113      }
 114  
 115      /**
 116       * Returns the minimum value an indicator calculation can return.
 117       *
 118       * @return float
 119       */
 120      public static function get_min_value() {
 121          return self::MIN_VALUE;
 122      }
 123  
 124      /**
 125       * Hook to allow indicators to pre-fill data that is shared accross time range calculations.
 126       *
 127       * Useful to fill analysable-dependant data that does not depend on the time ranges. Use
 128       * instance vars to cache data that can be re-used across samples calculations but changes
 129       * between time ranges (indicator instances are reset between time ranges to avoid unexpected
 130       * problems).
 131       *
 132       * You are also responsible of emptying previous analysable caches.
 133       *
 134       * @param \core_analytics\analysable $analysable
 135       * @return void
 136       */
 137      public function fill_per_analysable_caches(\core_analytics\analysable $analysable) {
 138      }
 139  
 140      /**
 141       * Calculates the indicator.
 142       *
 143       * Returns an array of values which size matches $sampleids size.
 144       *
 145       * @param int[] $sampleids
 146       * @param string $samplesorigin
 147       * @param integer $starttime Limit the calculation to this timestart
 148       * @param integer $endtime Limit the calculation to this timeend
 149       * @param array $existingcalculations Existing calculations of this indicator, indexed by sampleid.
 150       * @return array [0] = [$sampleid => int[]|float[]], [1] = [$sampleid => int|float], [2] = [$sampleid => $sampleid]
 151       */
 152      public function calculate($sampleids, $samplesorigin, $starttime = false, $endtime = false, $existingcalculations = array()) {
 153  
 154          if (!PHPUNIT_TEST && CLI_SCRIPT) {
 155              echo '.';
 156          }
 157  
 158          $calculations = array();
 159          $newcalculations = array();
 160          $notnulls = array();
 161          foreach ($sampleids as $sampleid => $unusedsampleid) {
 162  
 163              if (isset($existingcalculations[$sampleid])) {
 164                  $calculatedvalue = $existingcalculations[$sampleid];
 165              } else {
 166                  $calculatedvalue = $this->calculate_sample($sampleid, $samplesorigin, $starttime, $endtime);
 167                  $newcalculations[$sampleid] = $calculatedvalue;
 168              }
 169  
 170              if (!is_null($calculatedvalue)) {
 171                  $notnulls[$sampleid] = $sampleid;
 172                  $this->validate_calculated_value($calculatedvalue);
 173              }
 174  
 175              $calculations[$sampleid] = $calculatedvalue;
 176          }
 177  
 178          $features = $this->to_features($calculations);
 179  
 180          return array($features, $newcalculations, $notnulls);
 181      }
 182  
 183      /**
 184       * Validates the calculated value.
 185       *
 186       * @throws \coding_exception
 187       * @param float $calculatedvalue
 188       * @return true
 189       */
 190      protected function validate_calculated_value($calculatedvalue) {
 191          if ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE) {
 192              throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
 193                  ' and lower than ' . self::MAX_VALUE . ' ' . $calculatedvalue . ' received');
 194          }
 195          return true;
 196      }
 197  }