Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 * Analysers base class. 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\analyser; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Analysers base class. 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 { 37 38 /** 39 * @var int 40 */ 41 protected $modelid; 42 43 /** 44 * The model target. 45 * 46 * @var \core_analytics\local\target\base 47 */ 48 protected $target; 49 50 /** 51 * The model indicators. 52 * 53 * @var \core_analytics\local\indicator\base[] 54 */ 55 protected $indicators; 56 57 /** 58 * Time splitting methods to use. 59 * 60 * Multiple time splitting methods during evaluation and 1 single 61 * time splitting method once the model is enabled. 62 * 63 * @var \core_analytics\local\time_splitting\base[] 64 */ 65 protected $timesplittings; 66 67 /** 68 * Execution options. 69 * 70 * @var array 71 */ 72 protected $options; 73 74 /** 75 * Simple log array. 76 * 77 * @var string[] 78 */ 79 protected $log; 80 81 /** 82 * Constructor method. 83 * 84 * @param int $modelid 85 * @param \core_analytics\local\target\base $target 86 * @param \core_analytics\local\indicator\base[] $indicators 87 * @param \core_analytics\local\time_splitting\base[] $timesplittings 88 * @param array $options 89 * @return void 90 */ 91 public function __construct($modelid, \core_analytics\local\target\base $target, $indicators, $timesplittings, $options) { 92 $this->modelid = $modelid; 93 $this->target = $target; 94 $this->indicators = $indicators; 95 $this->timesplittings = $timesplittings; 96 97 if (empty($options['evaluation'])) { 98 $options['evaluation'] = false; 99 } 100 $this->options = $options; 101 102 // Checks if the analyser satisfies the indicators requirements. 103 $this->check_indicators_requirements(); 104 105 $this->log = array(); 106 } 107 108 /** 109 * Returns the list of analysable elements available on the site. 110 * 111 * \core_analytics\local\analyser\by_course and \core_analytics\local\analyser\sitewide are implementing 112 * this method returning site courses (by_course) and the whole system (sitewide) as analysables. 113 * 114 * @todo MDL-65284 This will be removed in Moodle 3.11 115 * @deprecated 116 * @see get_analysables_iterator 117 * @throws \coding_exception 118 * @return \core_analytics\analysable[] Array of analysable elements using the analysable id as array key. 119 */ 120 public function get_analysables() { 121 // This function should only be called from get_analysables_iterator and we keep it here until Moodle 3.11 122 // for backwards compatibility. 123 throw new \coding_exception('This method is deprecated in favour of get_analysables_iterator.'); 124 } 125 126 /** 127 * Returns the list of analysable elements available on the site. 128 * 129 * A relatively complex SQL query should be set so that we take into account which analysable elements 130 * have already been processed and the order in which they have been processed. Helper methods are available 131 * to ease to implementation of get_analysables_iterator: get_iterator_sql and order_sql. 132 * 133 * @param string|null $action 'prediction', 'training' or null if no specific action needed. 134 * @param \context[] $contexts Only analysables that depend on the provided contexts. All analysables in the system if empty. 135 * @return \Iterator 136 */ 137 public function get_analysables_iterator(?string $action = null, array $contexts = []) { 138 139 debugging('Please overwrite get_analysables_iterator with your own implementation, we only keep this default 140 implementation for backwards compatibility purposes with get_analysables(). note that $action param will 141 be ignored so the analysable elements will be processed using get_analysables order, regardless of the 142 last time they were processed.'); 143 144 return new \ArrayIterator($this->get_analysables()); 145 } 146 147 /** 148 * This function returns this analysable list of samples. 149 * 150 * @param \core_analytics\analysable $analysable 151 * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) 152 */ 153 abstract public function get_all_samples(\core_analytics\analysable $analysable); 154 155 /** 156 * This function returns the samples data from a list of sample ids. 157 * 158 * @param int[] $sampleids 159 * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) 160 */ 161 abstract public function get_samples($sampleids); 162 163 /** 164 * Returns the analysable of a sample. 165 * 166 * @param int $sampleid 167 * @return \core_analytics\analysable 168 */ 169 abstract public function get_sample_analysable($sampleid); 170 171 /** 172 * Returns the sample's origin in moodle database. 173 * 174 * @return string 175 */ 176 abstract public function get_samples_origin(); 177 178 /** 179 * Returns the context of a sample. 180 * 181 * moodle/analytics:listinsights will be required at this level to access the sample predictions. 182 * 183 * @param int $sampleid 184 * @return \context 185 */ 186 abstract public function sample_access_context($sampleid); 187 188 /** 189 * Describes a sample with a description summary and a \renderable (an image for example) 190 * 191 * @param int $sampleid 192 * @param int $contextid 193 * @param array $sampledata 194 * @return array array(string, \renderable) 195 */ 196 abstract public function sample_description($sampleid, $contextid, $sampledata); 197 198 /** 199 * Model id getter. 200 * @return int 201 */ 202 public function get_modelid(): int { 203 return $this->modelid; 204 } 205 206 /** 207 * Options getter. 208 * @return array 209 */ 210 public function get_options(): array { 211 return $this->options; 212 } 213 214 /** 215 * Returns the analysed target. 216 * 217 * @return \core_analytics\local\target\base 218 */ 219 public function get_target(): \core_analytics\local\target\base { 220 return $this->target; 221 } 222 223 /** 224 * Getter for time splittings. 225 * 226 * @return \core_analytics\local\time_splitting\base 227 */ 228 public function get_timesplittings(): array { 229 return $this->timesplittings; 230 } 231 232 /** 233 * Getter for indicators. 234 * 235 * @return \core_analytics\local\indicator\base 236 */ 237 public function get_indicators(): array { 238 return $this->indicators; 239 } 240 241 /** 242 * Instantiate the indicators. 243 * 244 * @return \core_analytics\local\indicator\base[] 245 */ 246 public function instantiate_indicators() { 247 foreach ($this->indicators as $key => $indicator) { 248 $this->indicators[$key] = call_user_func(array($indicator, 'instance')); 249 } 250 251 // Free memory ASAP. 252 gc_collect_cycles(); 253 gc_mem_caches(); 254 255 return $this->indicators; 256 } 257 258 /** 259 * Samples data this analyser provides. 260 * 261 * @return string[] 262 */ 263 protected function provided_sample_data() { 264 return array($this->get_samples_origin()); 265 } 266 267 /** 268 * Returns labelled data (training and evaluation). 269 * 270 * @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null. 271 * @return \stored_file[] 272 */ 273 public function get_labelled_data(array $contexts = []) { 274 // Delegates all processing to the analysis. 275 $result = new \core_analytics\local\analysis\result_file($this->get_modelid(), true, $this->get_options()); 276 $analysis = new \core_analytics\analysis($this, true, $result); 277 $analysis->run($contexts); 278 return $result->get(); 279 } 280 281 /** 282 * Returns unlabelled data (prediction). 283 * 284 * @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null. 285 * @return \stored_file[] 286 */ 287 public function get_unlabelled_data(array $contexts = []) { 288 // Delegates all processing to the analysis. 289 $result = new \core_analytics\local\analysis\result_file($this->get_modelid(), false, $this->get_options()); 290 $analysis = new \core_analytics\analysis($this, false, $result); 291 $analysis->run($contexts); 292 return $result->get(); 293 } 294 295 /** 296 * Returns indicator calculations as an array. 297 * 298 * @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null. 299 * @return array 300 */ 301 public function get_static_data(array $contexts = []) { 302 // Delegates all processing to the analysis. 303 $result = new \core_analytics\local\analysis\result_array($this->get_modelid(), false, $this->get_options()); 304 $analysis = new \core_analytics\analysis($this, false, $result); 305 $analysis->run($contexts); 306 return $result->get(); 307 } 308 309 /** 310 * Checks if the analyser satisfies all the model indicators requirements. 311 * 312 * @throws \core_analytics\requirements_exception 313 * @return void 314 */ 315 protected function check_indicators_requirements() { 316 317 foreach ($this->indicators as $indicator) { 318 $missingrequired = $this->check_indicator_requirements($indicator); 319 if ($missingrequired !== true) { 320 throw new \core_analytics\requirements_exception(get_class($indicator) . ' indicator requires ' . 321 json_encode($missingrequired) . ' sample data which is not provided by ' . get_class($this)); 322 } 323 } 324 } 325 326 /** 327 * Checks that this analyser satisfies the provided indicator requirements. 328 * 329 * @param \core_analytics\local\indicator\base $indicator 330 * @return true|string[] True if all good, missing requirements list otherwise 331 */ 332 public function check_indicator_requirements(\core_analytics\local\indicator\base $indicator) { 333 334 $providedsampledata = $this->provided_sample_data(); 335 336 $requiredsampledata = $indicator::required_sample_data(); 337 if (empty($requiredsampledata)) { 338 // The indicator does not need any sample data. 339 return true; 340 } 341 $missingrequired = array_diff($requiredsampledata, $providedsampledata); 342 343 if (empty($missingrequired)) { 344 return true; 345 } 346 347 return $missingrequired; 348 } 349 350 /** 351 * Adds a register to the analysis log. 352 * 353 * @param string $string 354 * @return void 355 */ 356 public function add_log($string) { 357 $this->log[] = $string; 358 } 359 360 /** 361 * Returns the analysis logs. 362 * 363 * @return string[] 364 */ 365 public function get_logs() { 366 return $this->log; 367 } 368 369 /** 370 * Whether the plugin needs user data clearing or not. 371 * 372 * This is related to privacy. Override this method if your analyser samples have any relation 373 * to the 'user' database entity. We need to clean the site from all user-related data if a user 374 * request their data to be deleted from the system. A static::provided_sample_data returning 'user' 375 * is an indicator that you should be returning true. 376 * 377 * @return bool 378 */ 379 public function processes_user_data() { 380 return false; 381 } 382 383 /** 384 * SQL JOIN from a sample to users table. 385 * 386 * This function should be defined if static::processes_user_data returns true and it is related to analytics API 387 * privacy API implementation. It allows the analytics API to identify data associated to users that needs to be 388 * deleted or exported. 389 * 390 * This function receives the alias of a table with a 'sampleid' field and it should return a SQL join 391 * with static::get_samples_origin and with 'user' table. Note that: 392 * - The function caller expects the returned 'user' table to be aliased as 'u' (defacto standard in moodle). 393 * - You can join with other tables if your samples origin table does not contain a 'userid' field (if that would be 394 * a requirement this solution would be automated for you) you can't though use the following 395 * aliases: 'ap', 'apa', 'aic' and 'am'. 396 * 397 * Some examples: 398 * 399 * static::get_samples_origin() === 'user': 400 * JOIN {user} u ON {$sampletablealias}.sampleid = u.id 401 * 402 * static::get_samples_origin() === 'role_assignments': 403 * JOIN {role_assignments} ra ON {$sampletablealias}.sampleid = ra.userid JOIN {user} u ON u.id = ra.userid 404 * 405 * static::get_samples_origin() === 'user_enrolments': 406 * JOIN {user_enrolments} ue ON {$sampletablealias}.sampleid = ue.userid JOIN {user} u ON u.id = ue.userid 407 * 408 * @throws \coding_exception 409 * @param string $sampletablealias The alias of the table with a sampleid field that will join with this SQL string 410 * @return string 411 */ 412 public function join_sample_user($sampletablealias) { 413 throw new \coding_exception('This method should be implemented if static::processes_user_data returns true.'); 414 } 415 416 /** 417 * Do this analyser's analysables have 1 single sample each? 418 * 419 * Overwrite and return true if your analysables only have 420 * one sample. The insights generated by models using this 421 * analyser will then include the suggested actions in the 422 * notification. 423 * 424 * @return bool 425 */ 426 public static function one_sample_per_analysable() { 427 return false; 428 } 429 430 /** 431 * Returns an array of context levels that can be used to restrict the contexts used during analysis. 432 * 433 * The contexts provided to self::get_analysables_iterator will match these contextlevels. 434 * 435 * @return array Array of context levels or an empty array if context restriction is not supported. 436 */ 437 public static function context_restriction_support(): array { 438 return []; 439 } 440 441 /** 442 * Returns the possible contexts used by the analyser. 443 * 444 * This method uses separate logic for each context level because to iterate through 445 * the list of contexts calling get_context_name for each of them would be expensive 446 * in performance terms. 447 * 448 * This generic implementation returns all the contexts in the site for the provided context level. 449 * Overwrite it for specific restrictions in your analyser. 450 * 451 * @param string|null $query Context name filter. 452 * @return int[] 453 */ 454 public static function potential_context_restrictions(string $query = null) { 455 return \core_analytics\manager::get_potential_context_restrictions(static::context_restriction_support(), $query); 456 } 457 458 /** 459 * Get the sql of a default implementation of the iterator. 460 * 461 * This method only works for analysers that return analysable elements which ids map to a context instance ids. 462 * 463 * @param string $tablename The name of the table 464 * @param int $contextlevel The context level of the analysable 465 * @param string|null $action 466 * @param string|null $tablealias The table alias 467 * @param \context[] $contexts Only analysables that depend on the provided contexts. All analysables if empty. 468 * @return array [0] => sql and [1] => params array 469 */ 470 protected function get_iterator_sql(string $tablename, int $contextlevel, ?string $action = null, ?string $tablealias = null, 471 array $contexts = []) { 472 global $DB; 473 474 if (!$tablealias) { 475 $tablealias = 'analysable'; 476 } 477 478 $params = ['contextlevel' => $contextlevel, 'modelid' => $this->get_modelid()]; 479 $select = $tablealias . '.*, ' . \context_helper::get_preload_record_columns_sql('ctx'); 480 481 // We add the action filter on ON instead of on WHERE because otherwise records are not returned if there are existing 482 // records for another action or model. 483 $usedanalysablesjoin = ' LEFT JOIN {analytics_used_analysables} aua ON ' . $tablealias . '.id = aua.analysableid AND ' . 484 '(aua.modelid = :modelid OR aua.modelid IS NULL)'; 485 486 if ($action) { 487 $usedanalysablesjoin .= " AND aua.action = :action"; 488 $params = $params + ['action' => $action]; 489 } 490 491 $sql = 'SELECT ' . $select . ' 492 FROM {' . $tablename . '} ' . $tablealias . ' 493 ' . $usedanalysablesjoin . ' 494 JOIN {context} ctx ON (ctx.contextlevel = :contextlevel AND ctx.instanceid = ' . $tablealias . '.id) '; 495 496 if (!$contexts) { 497 // Adding the 1 = 1 just to have the WHERE part so that all further conditions 498 // added by callers can be appended to $sql with and ' AND'. 499 $sql .= 'WHERE 1 = 1'; 500 } else { 501 502 $contextsqls = []; 503 foreach ($contexts as $context) { 504 $paramkey1 = 'paramctxlike' . $context->id; 505 $paramkey2 = 'paramctxeq' . $context->id; 506 $contextsqls[] = $DB->sql_like('ctx.path', ':' . $paramkey1); 507 $contextsqls[] = 'ctx.path = :' . $paramkey2; 508 509 // This includes the context itself. 510 $params[$paramkey1] = $context->path . '/%'; 511 $params[$paramkey2] = $context->path; 512 } 513 $sql .= 'WHERE (' . implode(' OR ', $contextsqls) . ')'; 514 } 515 516 return [$sql, $params]; 517 } 518 519 /** 520 * Returns the order by clause. 521 * 522 * @param string|null $fieldname The field name 523 * @param string $order 'ASC' or 'DESC' 524 * @param string|null $tablealias The table alias of the field 525 * @return string 526 */ 527 protected function order_sql(?string $fieldname = null, string $order = 'ASC', ?string $tablealias = null) { 528 529 if (!$tablealias) { 530 $tablealias = 'analysable'; 531 } 532 533 if ($order != 'ASC' && $order != 'DESC') { 534 throw new \coding_exception('The order can only be ASC or DESC'); 535 } 536 537 $ordersql = ' ORDER BY (CASE WHEN aua.timeanalysed IS NULL THEN 0 ELSE aua.timeanalysed END) ASC'; 538 if ($fieldname) { 539 $ordersql .= ', ' . $tablealias . '.' . $fieldname .' ' . $order; 540 } 541 542 return $ordersql; 543 } 544 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body