Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Analysers base class.** @package core_analytics* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace core_analytics\local\analyser;defined('MOODLE_INTERNAL') || die();/*** Analysers base class.** @package core_analytics* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/abstract class base {/*** @var int*/protected $modelid;/*** The model target.** @var \core_analytics\local\target\base*/protected $target;/*** The model indicators.** @var \core_analytics\local\indicator\base[]*/protected $indicators;/*** Time splitting methods to use.** Multiple time splitting methods during evaluation and 1 single* time splitting method once the model is enabled.** @var \core_analytics\local\time_splitting\base[]*/protected $timesplittings;/*** Execution options.** @var array*/protected $options;/*** Simple log array.** @var string[]*/protected $log;/*** Constructor method.** @param int $modelid* @param \core_analytics\local\target\base $target* @param \core_analytics\local\indicator\base[] $indicators* @param \core_analytics\local\time_splitting\base[] $timesplittings* @param array $options* @return void*/public function __construct($modelid, \core_analytics\local\target\base $target, $indicators, $timesplittings, $options) {$this->modelid = $modelid;$this->target = $target;$this->indicators = $indicators;$this->timesplittings = $timesplittings;if (empty($options['evaluation'])) {$options['evaluation'] = false;}$this->options = $options;// Checks if the analyser satisfies the indicators requirements.$this->check_indicators_requirements();$this->log = array();}/*** @deprecated since Moodle 3.7*/public function get_analysables() {throw new \coding_exception('get_analysables() method has been removed and cannot be used any more.');}/*** Returns the list of analysable elements available on the site.** A relatively complex SQL query should be set so that we take into account which analysable elements* have already been processed and the order in which they have been processed. Helper methods are available* to ease to implementation of get_analysables_iterator: get_iterator_sql and order_sql.** @param string|null $action 'prediction', 'training' or null if no specific action needed.* @param \context[] $contexts Only analysables that depend on the provided contexts. All analysables in the system if empty.* @return \Iterator*/abstract public function get_analysables_iterator(?string $action = null, array $contexts = []);/*** This function returns this analysable list of samples.** @param \core_analytics\analysable $analysable* @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)*/abstract public function get_all_samples(\core_analytics\analysable $analysable);/*** This function returns the samples data from a list of sample ids.** @param int[] $sampleids* @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)*/abstract public function get_samples($sampleids);/*** Returns the analysable of a sample.** @param int $sampleid* @return \core_analytics\analysable*/abstract public function get_sample_analysable($sampleid);/*** Returns the sample's origin in moodle database.** @return string*/abstract public function get_samples_origin();/*** Returns the context of a sample.** moodle/analytics:listinsights will be required at this level to access the sample predictions.** @param int $sampleid* @return \context*/abstract public function sample_access_context($sampleid);/*** Describes a sample with a description summary and a \renderable (an image for example)** @param int $sampleid* @param int $contextid* @param array $sampledata* @return array array(string, \renderable)*/abstract public function sample_description($sampleid, $contextid, $sampledata);/*** Model id getter.* @return int*/public function get_modelid(): int {return $this->modelid;}/*** Options getter.* @return array*/public function get_options(): array {return $this->options;}/*** Returns the analysed target.** @return \core_analytics\local\target\base*/public function get_target(): \core_analytics\local\target\base {return $this->target;}/*** Getter for time splittings.** @return \core_analytics\local\time_splitting\base*/public function get_timesplittings(): array {return $this->timesplittings;}/*** Getter for indicators.** @return \core_analytics\local\indicator\base*/public function get_indicators(): array {return $this->indicators;}/*** Instantiate the indicators.** @return \core_analytics\local\indicator\base[]*/public function instantiate_indicators() {foreach ($this->indicators as $key => $indicator) {$this->indicators[$key] = call_user_func(array($indicator, 'instance'));}// Free memory ASAP.gc_collect_cycles();gc_mem_caches();return $this->indicators;}/*** Samples data this analyser provides.** @return string[]*/protected function provided_sample_data() {return array($this->get_samples_origin());}/*** Returns labelled data (training and evaluation).** @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null.* @return \stored_file[]*/public function get_labelled_data(array $contexts = []) {// Delegates all processing to the analysis.$result = new \core_analytics\local\analysis\result_file($this->get_modelid(), true, $this->get_options());$analysis = new \core_analytics\analysis($this, true, $result);$analysis->run($contexts);return $result->get();}/*** Returns unlabelled data (prediction).** @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null.* @return \stored_file[]*/public function get_unlabelled_data(array $contexts = []) {// Delegates all processing to the analysis.$result = new \core_analytics\local\analysis\result_file($this->get_modelid(), false, $this->get_options());$analysis = new \core_analytics\analysis($this, false, $result);$analysis->run($contexts);return $result->get();}/*** Returns indicator calculations as an array.** @param \context[] $contexts Restrict the analysis to these contexts. No context restrictions if null.* @return array*/public function get_static_data(array $contexts = []) {// Delegates all processing to the analysis.$result = new \core_analytics\local\analysis\result_array($this->get_modelid(), false, $this->get_options());$analysis = new \core_analytics\analysis($this, false, $result);$analysis->run($contexts);return $result->get();}/*** Checks if the analyser satisfies all the model indicators requirements.** @throws \core_analytics\requirements_exception* @return void*/protected function check_indicators_requirements() {foreach ($this->indicators as $indicator) {$missingrequired = $this->check_indicator_requirements($indicator);if ($missingrequired !== true) {throw new \core_analytics\requirements_exception(get_class($indicator) . ' indicator requires ' .json_encode($missingrequired) . ' sample data which is not provided by ' . get_class($this));}}}/*** Checks that this analyser satisfies the provided indicator requirements.** @param \core_analytics\local\indicator\base $indicator* @return true|string[] True if all good, missing requirements list otherwise*/public function check_indicator_requirements(\core_analytics\local\indicator\base $indicator) {$providedsampledata = $this->provided_sample_data();$requiredsampledata = $indicator::required_sample_data();if (empty($requiredsampledata)) {// The indicator does not need any sample data.return true;}$missingrequired = array_diff($requiredsampledata, $providedsampledata);if (empty($missingrequired)) {return true;}return $missingrequired;}/*** Adds a register to the analysis log.** @param string $string* @return void*/public function add_log($string) {$this->log[] = $string;}/*** Returns the analysis logs.** @return string[]*/public function get_logs() {return $this->log;}/*** Whether the plugin needs user data clearing or not.** This is related to privacy. Override this method if your analyser samples have any relation* to the 'user' database entity. We need to clean the site from all user-related data if a user* request their data to be deleted from the system. A static::provided_sample_data returning 'user'* is an indicator that you should be returning true.** @return bool*/public function processes_user_data() {return false;}/*** SQL JOIN from a sample to users table.** This function should be defined if static::processes_user_data returns true and it is related to analytics API* privacy API implementation. It allows the analytics API to identify data associated to users that needs to be* deleted or exported.** This function receives the alias of a table with a 'sampleid' field and it should return a SQL join* with static::get_samples_origin and with 'user' table. Note that:* - The function caller expects the returned 'user' table to be aliased as 'u' (defacto standard in moodle).* - You can join with other tables if your samples origin table does not contain a 'userid' field (if that would be* a requirement this solution would be automated for you) you can't though use the following* aliases: 'ap', 'apa', 'aic' and 'am'.** Some examples:** static::get_samples_origin() === 'user':* JOIN {user} u ON {$sampletablealias}.sampleid = u.id** static::get_samples_origin() === 'role_assignments':* JOIN {role_assignments} ra ON {$sampletablealias}.sampleid = ra.userid JOIN {user} u ON u.id = ra.userid** static::get_samples_origin() === 'user_enrolments':* JOIN {user_enrolments} ue ON {$sampletablealias}.sampleid = ue.userid JOIN {user} u ON u.id = ue.userid** @throws \coding_exception* @param string $sampletablealias The alias of the table with a sampleid field that will join with this SQL string* @return string*/public function join_sample_user($sampletablealias) {throw new \coding_exception('This method should be implemented if static::processes_user_data returns true.');}/*** Do this analyser's analysables have 1 single sample each?** Overwrite and return true if your analysables only have* one sample. The insights generated by models using this* analyser will then include the suggested actions in the* notification.** @return bool*/public static function one_sample_per_analysable() {return false;}/*** Returns an array of context levels that can be used to restrict the contexts used during analysis.** The contexts provided to self::get_analysables_iterator will match these contextlevels.** @return array Array of context levels or an empty array if context restriction is not supported.*/public static function context_restriction_support(): array {return [];}/*** Returns the possible contexts used by the analyser.** This method uses separate logic for each context level because to iterate through* the list of contexts calling get_context_name for each of them would be expensive* in performance terms.** This generic implementation returns all the contexts in the site for the provided context level.* Overwrite it for specific restrictions in your analyser.** @param string|null $query Context name filter.* @return int[]*/public static function potential_context_restrictions(string $query = null) {return \core_analytics\manager::get_potential_context_restrictions(static::context_restriction_support(), $query);}/*** Get the sql of a default implementation of the iterator.** This method only works for analysers that return analysable elements which ids map to a context instance ids.** @param string $tablename The name of the table* @param int $contextlevel The context level of the analysable* @param string|null $action* @param string|null $tablealias The table alias* @param \context[] $contexts Only analysables that depend on the provided contexts. All analysables if empty.* @return array [0] => sql and [1] => params array*/protected function get_iterator_sql(string $tablename, int $contextlevel, ?string $action = null, ?string $tablealias = null,array $contexts = []) {global $DB;if (!$tablealias) {$tablealias = 'analysable';}$params = ['contextlevel' => $contextlevel, 'modelid' => $this->get_modelid()];$select = $tablealias . '.*, ' . \context_helper::get_preload_record_columns_sql('ctx');// We add the action filter on ON instead of on WHERE because otherwise records are not returned if there are existing// records for another action or model.$usedanalysablesjoin = ' LEFT JOIN {analytics_used_analysables} aua ON ' . $tablealias . '.id = aua.analysableid AND ' .'(aua.modelid = :modelid OR aua.modelid IS NULL)';if ($action) {$usedanalysablesjoin .= " AND aua.action = :action";$params = $params + ['action' => $action];}$sql = 'SELECT ' . $select . 'FROM {' . $tablename . '} ' . $tablealias . '' . $usedanalysablesjoin . 'JOIN {context} ctx ON (ctx.contextlevel = :contextlevel AND ctx.instanceid = ' . $tablealias . '.id) ';if (!$contexts) {// Adding the 1 = 1 just to have the WHERE part so that all further conditions// added by callers can be appended to $sql with and ' AND'.$sql .= 'WHERE 1 = 1';} else {$contextsqls = [];foreach ($contexts as $context) {$paramkey1 = 'paramctxlike' . $context->id;$paramkey2 = 'paramctxeq' . $context->id;$contextsqls[] = $DB->sql_like('ctx.path', ':' . $paramkey1);$contextsqls[] = 'ctx.path = :' . $paramkey2;// This includes the context itself.$params[$paramkey1] = $context->path . '/%';$params[$paramkey2] = $context->path;}$sql .= 'WHERE (' . implode(' OR ', $contextsqls) . ')';}return [$sql, $params];}/*** Returns the order by clause.** @param string|null $fieldname The field name* @param string $order 'ASC' or 'DESC'* @param string|null $tablealias The table alias of the field* @return string*/protected function order_sql(?string $fieldname = null, string $order = 'ASC', ?string $tablealias = null) {if (!$tablealias) {$tablealias = 'analysable';}if ($order != 'ASC' && $order != 'DESC') {throw new \coding_exception('The order can only be ASC or DESC');}$ordersql = ' ORDER BY (CASE WHEN aua.timeanalysed IS NULL THEN 0 ELSE aua.timeanalysed END) ASC';if ($fieldname) {$ordersql .= ', ' . $tablealias . '.' . $fieldname .' ' . $order;}return $ordersql;}}