Merge branch 'MDL-81449-main' of https://github.com/lucaboesch/moodle
[moodle.git] / analytics / classes / prediction.php
blob3b0aced6c9df9876dd2505531f0b2a17cff085ab
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.
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/>.
17 /**
18 * Representation of a prediction.
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
25 namespace core_analytics;
27 defined('MOODLE_INTERNAL') || die();
29 /**
30 * Representation of a prediction.
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
36 class prediction {
38 /**
39 * Prediction details (one of the default prediction actions)
41 const ACTION_PREDICTION_DETAILS = 'predictiondetails';
43 /**
44 * Prediction useful (one of the default prediction actions)
46 const ACTION_USEFUL = 'useful';
48 /**
49 * Prediction not useful (one of the default prediction actions)
51 const ACTION_NOT_USEFUL = 'notuseful';
53 /**
54 * Prediction already fixed (one of the default prediction actions)
56 const ACTION_FIXED = 'fixed';
58 /**
59 * Prediction not applicable.
61 const ACTION_NOT_APPLICABLE = 'notapplicable';
63 /**
64 * Prediction incorrectly flagged.
66 const ACTION_INCORRECTLY_FLAGGED = 'incorrectlyflagged';
68 /**
69 * @var \stdClass
71 private $prediction;
73 /**
74 * @var array
76 private $sampledata;
78 /**
79 * @var array
81 private $calculations = array();
83 /**
84 * Constructor
86 * @param \stdClass|int $prediction
87 * @param array $sampledata
88 * @return void
90 public function __construct($prediction, $sampledata) {
91 global $DB;
93 if (is_scalar($prediction)) {
94 $prediction = $DB->get_record('analytics_predictions', array('id' => $prediction), '*', MUST_EXIST);
96 $this->prediction = $prediction;
98 $this->sampledata = $sampledata;
100 $this->format_calculations();
104 * Get prediction object data.
106 * @return \stdClass
108 public function get_prediction_data() {
109 return $this->prediction;
113 * Get prediction sample data.
115 * @return array
117 public function get_sample_data() {
118 return $this->sampledata;
122 * Gets the prediction calculations
124 * @return array
126 public function get_calculations() {
127 return $this->calculations;
131 * Stores the executed action.
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.
136 * @param string $actionname
137 * @param \core_analytics\local\target\base $target
139 public function action_executed($actionname, \core_analytics\local\target\base $target) {
140 global $USER, $DB;
142 $context = \context::instance_by_id($this->get_prediction_data()->contextid, IGNORE_MISSING);
143 if (!$context) {
144 throw new \moodle_exception('errorpredictioncontextnotavailable', 'analytics');
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;
154 $bulkactions = $target->bulk_actions([$this]);
155 foreach ($bulkactions as $action) {
156 if ($action->get_action_name() === $actionname) {
157 $found = true;
160 if (empty($found)) {
161 throw new \moodle_exception('errorunknownaction', 'analytics');
164 $predictionid = $this->get_prediction_data()->id;
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);
173 $eventdata = array (
174 'context' => $context,
175 'objectid' => $predictionid,
176 'other' => array('actionname' => $actionname)
178 \core\event\prediction_action_started::create($eventdata)->trigger();
182 * Get the executed actions.
184 * Actions could be filtered by actionname.
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.
190 public function get_executed_actions(array $actionnamefilter = null, int $userid = 0): array {
191 global $USER, $DB;
193 $conditions[] = "predictionid = :predictionid";
194 $params['predictionid'] = $this->get_prediction_data()->id;
195 if (!$userid) {
196 $userid = $USER->id;
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;
205 return $DB->get_records_select('analytics_prediction_actions', implode(' AND ', $conditions), $params);
209 * format_calculations
211 * @return \stdClass[]
213 private function format_calculations() {
215 $calculations = json_decode($this->prediction->calculations, true);
217 foreach ($calculations as $featurename => $value) {
219 list($indicatorclass, $subtype) = $this->parse_feature_name($featurename);
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');
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;
236 * parse_feature_name
238 * @param string $featurename
239 * @return string[]
241 private function parse_feature_name($featurename) {
243 $indicatorclass = $featurename;
244 $subtype = false;
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);
253 return array($indicatorclass, $subtype);