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.
   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   * Representation of a prediction.
  19   *
  20   * @package   core_analytics
  21   * @copyright 2017 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;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Representation of a prediction.
  31   *
  32   * @package   core_analytics
  33   * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class prediction {
  37  
  38      /**
  39       * Prediction details (one of the default prediction actions)
  40       */
  41      const ACTION_PREDICTION_DETAILS = 'predictiondetails';
  42  
  43      /**
  44       * Prediction useful (one of the default prediction actions)
  45       */
  46      const ACTION_USEFUL = 'useful';
  47  
  48      /**
  49       * Prediction not useful (one of the default prediction actions)
  50       */
  51      const ACTION_NOT_USEFUL = 'notuseful';
  52  
  53      /**
  54       * Prediction already fixed (one of the default prediction actions)
  55       */
  56      const ACTION_FIXED = 'fixed';
  57  
  58      /**
  59       * Prediction not applicable.
  60       */
  61      const ACTION_NOT_APPLICABLE = 'notapplicable';
  62  
  63      /**
  64       * Prediction incorrectly flagged.
  65       */
  66      const ACTION_INCORRECTLY_FLAGGED = 'incorrectlyflagged';
  67  
  68      /**
  69       * @var \stdClass
  70       */
  71      private $prediction;
  72  
  73      /**
  74       * @var array
  75       */
  76      private $sampledata;
  77  
  78      /**
  79       * @var array
  80       */
  81      private $calculations = array();
  82  
  83      /**
  84       * Constructor
  85       *
  86       * @param \stdClass|int $prediction
  87       * @param array $sampledata
  88       * @return void
  89       */
  90      public function __construct($prediction, $sampledata) {
  91          global $DB;
  92  
  93          if (is_scalar($prediction)) {
  94              $prediction = $DB->get_record('analytics_predictions', array('id' => $prediction), '*', MUST_EXIST);
  95          }
  96          $this->prediction = $prediction;
  97  
  98          $this->sampledata = $sampledata;
  99  
 100          $this->format_calculations();
 101      }
 102  
 103      /**
 104       * Get prediction object data.
 105       *
 106       * @return \stdClass
 107       */
 108      public function get_prediction_data() {
 109          return $this->prediction;
 110      }
 111  
 112      /**
 113       * Get prediction sample data.
 114       *
 115       * @return array
 116       */
 117      public function get_sample_data() {
 118          return $this->sampledata;
 119      }
 120  
 121      /**
 122       * Gets the prediction calculations
 123       *
 124       * @return array
 125       */
 126      public function get_calculations() {
 127          return $this->calculations;
 128      }
 129  
 130      /**
 131       * Stores the executed action.
 132  
 133       * Prediction instances should be retrieved using \core_analytics\manager::get_prediction,
 134       * It is the caller responsability to check that the user can see the prediction.
 135       *
 136       * @param string $actionname
 137       * @param \core_analytics\local\target\base $target
 138       */
 139      public function action_executed($actionname, \core_analytics\local\target\base $target) {
 140          global $USER, $DB;
 141  
 142          $context = \context::instance_by_id($this->get_prediction_data()->contextid, IGNORE_MISSING);
 143          if (!$context) {
 144              throw new \moodle_exception('errorpredictioncontextnotavailable', 'analytics');
 145          }
 146  
 147          // Check that the provided action exists.
 148          $actions = $target->prediction_actions($this, true);
 149          foreach ($actions as $action) {
 150              if ($action->get_action_name() === $actionname) {
 151                  $found = true;
 152              }
 153          }
 154          $bulkactions = $target->bulk_actions([$this]);
 155          foreach ($bulkactions as $action) {
 156              if ($action->get_action_name() === $actionname) {
 157                  $found = true;
 158              }
 159          }
 160          if (empty($found)) {
 161              throw new \moodle_exception('errorunknownaction', 'analytics');
 162          }
 163  
 164          $predictionid = $this->get_prediction_data()->id;
 165  
 166          $action = new \stdClass();
 167          $action->predictionid = $predictionid;
 168          $action->userid = $USER->id;
 169          $action->actionname = $actionname;
 170          $action->timecreated = time();
 171          $DB->insert_record('analytics_prediction_actions', $action);
 172  
 173          $eventdata = array (
 174              'context' => $context,
 175              'objectid' => $predictionid,
 176              'other' => array('actionname' => $actionname)
 177          );
 178          \core\event\prediction_action_started::create($eventdata)->trigger();
 179      }
 180  
 181      /**
 182       * Get the executed actions.
 183       *
 184       * Actions could be filtered by actionname.
 185       *
 186       * @param array $actionnamefilter Limit the results obtained to this list of action names.
 187       * @param int $userid the user id. Current user by default.
 188       * @return array of actions.
 189       */
 190      public function get_executed_actions(array $actionnamefilter = null, int $userid = 0): array {
 191          global $USER, $DB;
 192  
 193          $conditions[] = "predictionid = :predictionid";
 194          $params['predictionid'] = $this->get_prediction_data()->id;
 195          if (!$userid) {
 196              $userid = $USER->id;
 197          }
 198          $conditions[] = "userid = :userid";
 199          $params['userid'] = $userid;
 200          if ($actionnamefilter) {
 201              list($actionsql, $actionparams) = $DB->get_in_or_equal($actionnamefilter, SQL_PARAMS_NAMED);
 202              $conditions[] = "actionname $actionsql";
 203              $params = $params + $actionparams;
 204          }
 205          return $DB->get_records_select('analytics_prediction_actions', implode(' AND ', $conditions), $params);
 206      }
 207  
 208      /**
 209       * format_calculations
 210       *
 211       * @return \stdClass[]
 212       */
 213      private function format_calculations() {
 214  
 215          $calculations = json_decode($this->prediction->calculations, true);
 216  
 217          foreach ($calculations as $featurename => $value) {
 218  
 219              list($indicatorclass, $subtype) = $this->parse_feature_name($featurename);
 220  
 221              if ($indicatorclass === 'range') {
 222                  // Time range indicators don't belong to any indicator class, we don't store them.
 223                  continue;
 224              } else if (!\core_analytics\manager::is_valid($indicatorclass, '\core_analytics\local\indicator\base')) {
 225                  throw new \moodle_exception('errorpredictionformat', 'analytics');
 226              }
 227  
 228              $this->calculations[$featurename] = new \stdClass();
 229              $this->calculations[$featurename]->subtype = $subtype;
 230              $this->calculations[$featurename]->indicator = \core_analytics\manager::get_indicator($indicatorclass);
 231              $this->calculations[$featurename]->value = $value;
 232          }
 233      }
 234  
 235      /**
 236       * parse_feature_name
 237       *
 238       * @param string $featurename
 239       * @return string[]
 240       */
 241      private function parse_feature_name($featurename) {
 242  
 243          $indicatorclass = $featurename;
 244          $subtype = false;
 245  
 246          // Some indicator result in more than 1 feature, we need to see which feature are we dealing with.
 247          $separatorpos = strpos($featurename, '/');
 248          if ($separatorpos !== false) {
 249              $subtype = substr($featurename, ($separatorpos + 1));
 250              $indicatorclass = substr($featurename, 0, $separatorpos);
 251          }
 252  
 253          return array($indicatorclass, $subtype);
 254      }
 255  }