first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,55 @@
<?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/>.
/**
* Helper class that contains helper functions for cli scripts.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics;
defined('MOODLE_INTERNAL') || die();
/**
* Helper class that contains helper functions for cli scripts.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class clihelper {
/**
* List all models in the system. To be used from cli scripts.
*
* @return void
*/
public static function list_models() {
cli_heading("List of models");
echo str_pad(get_string('modelid', 'tool_analytics'), 15, ' ') . ' ' . str_pad(get_string('name'), 50, ' ') .
' ' . str_pad(get_string('status'), 15, ' ') . "\n";
$models = \core_analytics\manager::get_all_models();
foreach ($models as $model) {
$modelid = $model->get_id();
$isenabled = $model->is_enabled() ? get_string('enabled', 'tool_analytics') : get_string('disabled', 'tool_analytics');
$name = $model->get_name();
echo str_pad($modelid, 15, ' ') . ' ' . str_pad($name, 50, ' ') . ' ' . str_pad($isenabled, 15, ' ') . "\n";
}
}
}
+109
View File
@@ -0,0 +1,109 @@
<?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/>.
/**
* This is the external API for this component.
*
* @package tool_analytics
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
/**
* This is the external API for this component.
*
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
const MAX_CONTEXTS_RETURNED = 100;
/**
* potential_contexts parameters.
*
* @since Moodle 3.8
* @return external_function_parameters
*/
public static function potential_contexts_parameters() {
return new external_function_parameters(
array(
'query' => new external_value(PARAM_NOTAGS, 'The model id', VALUE_DEFAULT),
'modelid' => new external_value(PARAM_INT, 'The model id', VALUE_DEFAULT)
)
);
}
/**
* Return the contexts that match the provided query.
*
* @since Moodle 3.8
* @param string|null $query
* @param int|null $modelid
* @return array an array of contexts
*/
public static function potential_contexts(?string $query = null, ?int $modelid = null) {
$params = self::validate_parameters(self::potential_contexts_parameters(), ['modelid' => $modelid, 'query' => $query]);
\core_analytics\manager::check_can_manage_models();
if ($params['modelid']) {
$model = new \core_analytics\model($params['modelid']);
$contexts = ($model->get_analyser(['notimesplitting' => true]))::potential_context_restrictions($params['query']);
} else {
$contexts = \core_analytics\manager::get_potential_context_restrictions(null, $params['query']);
}
$contextoptions = [];
$i = 0;
foreach ($contexts as $contextid => $contextname) {
if ($i === self::MAX_CONTEXTS_RETURNED) {
// Limited to MAX_CONTEXTS_RETURNED items.
break;
}
$contextoptions[] = ['id' => $contextid, 'name' => $contextname];
$i++;
}
return $contextoptions;
}
/**
* potential_contexts return
*
* @since Moodle 3.8
* @return \core_external\external_description
*/
public static function potential_contexts_returns() {
return new external_multiple_structure(
new external_single_structure([
'id' => new external_value(PARAM_INT, 'ID of the context'),
'name' => new external_value(PARAM_NOTAGS, 'The context name')
])
);
}
}
@@ -0,0 +1,235 @@
<?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/>.
/**
* Model edit form.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Model edit form.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edit_model extends \moodleform {
/**
* Form definition
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
if ($this->_customdata['trainedmodel'] && $this->_customdata['staticmodel'] === false) {
$message = get_string('edittrainedwarning', 'tool_analytics');
$mform->addElement('html', $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING));
}
$mform->addElement('advcheckbox', 'enabled', get_string('enabled', 'tool_analytics'));
// Target.
if (!empty($this->_customdata['targets'])) {
$targets = array('' => '');
foreach ($this->_customdata['targets'] as $classname => $target) {
$optionname = \tool_analytics\output\helper::class_to_option($classname);
$targets[$optionname] = $target->get_name();
}
$mform->addElement('select', 'target', get_string('target', 'tool_analytics'), $targets);
$mform->addHelpButton('target', 'target', 'tool_analytics');
$mform->addRule('target', get_string('required'), 'required', null, 'client');
}
if (!empty($this->_customdata['targetname']) && !empty($this->_customdata['targetclass'])) {
$mform->addElement('static', 'targetname', get_string('target', 'tool_analytics'), $this->_customdata['targetname']);
$mform->addElement('hidden', 'target',
\tool_analytics\output\helper::class_to_option($this->_customdata['targetclass']));
// We won't update the model's target so no worries about its format (we can't use PARAM_ALPHANUMEXT
// because of class_to_option).
$mform->setType('target', PARAM_TEXT);
}
// Indicators.
if (!$this->_customdata['staticmodel']) {
$indicators = array();
foreach ($this->_customdata['indicators'] as $classname => $indicator) {
$optionname = \tool_analytics\output\helper::class_to_option($classname);
$indicators[$optionname] = $indicator->get_name();
}
$options = array(
'multiple' => true
);
$mform->addElement('autocomplete', 'indicators', get_string('indicators', 'tool_analytics'), $indicators, $options);
$mform->setType('indicators', PARAM_ALPHANUMEXT);
$mform->addHelpButton('indicators', 'indicators', 'tool_analytics');
}
// Time-splitting methods.
if (!empty($this->_customdata['invalidcurrenttimesplitting'])) {
$mform->addElement('html', $OUTPUT->notification(
get_string('invalidcurrenttimesplitting', 'tool_analytics'),
\core\output\notification::NOTIFY_WARNING)
);
}
$timesplittings = array('' => '');
foreach ($this->_customdata['timesplittings'] as $classname => $timesplitting) {
$optionname = \tool_analytics\output\helper::class_to_option($classname);
$timesplittings[$optionname] = $timesplitting->get_name();
}
$mform->addElement('select', 'timesplitting', get_string('timesplittingmethod', 'analytics'), $timesplittings);
$mform->addHelpButton('timesplitting', 'timesplittingmethod', 'analytics');
// Contexts restriction.
if (!empty($this->_customdata['supportscontexts'])) {
$options = [
'ajax' => 'tool_analytics/potential-contexts',
'multiple' => true,
'noselectionstring' => get_string('all')
];
if (!empty($this->_customdata['id'])) {
$options['modelid'] = $this->_customdata['id'];
$contexts = $this->load_current_contexts();
} else {
// No need to preload any selected contexts.
$contexts = [];
}
$mform->addElement('autocomplete', 'contexts', get_string('contexts', 'tool_analytics'), $contexts, $options);
$mform->setType('contexts', PARAM_INT);
$mform->addHelpButton('contexts', 'contexts', 'tool_analytics');
}
// Predictions processor.
if (!$this->_customdata['staticmodel']) {
$defaultprocessor = \core_analytics\manager::get_predictions_processor_name(
\core_analytics\manager::get_predictions_processor()
);
$predictionprocessors = ['' => get_string('defaultpredictoroption', 'analytics', $defaultprocessor)];
foreach ($this->_customdata['predictionprocessors'] as $classname => $predictionsprocessor) {
if ($predictionsprocessor->is_ready() !== true) {
continue;
}
$optionname = \tool_analytics\output\helper::class_to_option($classname);
$predictionprocessors[$optionname] = \core_analytics\manager::get_predictions_processor_name($predictionsprocessor);
}
$mform->addElement('select', 'predictionsprocessor', get_string('predictionsprocessor', 'analytics'),
$predictionprocessors);
$mform->addHelpButton('predictionsprocessor', 'predictionsprocessor', 'analytics');
}
if (!empty($this->_customdata['id'])) {
$mform->addElement('hidden', 'id', $this->_customdata['id']);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'action', 'edit');
$mform->setType('action', PARAM_ALPHANUMEXT);
}
$this->add_action_buttons();
}
/**
* Form validation
*
* @param array $data data from the form.
* @param array $files files uploaded.
*
* @return array of errors.
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
$targetclass = \tool_analytics\output\helper::option_to_class($data['target']);
$target = \core_analytics\manager::get_target($targetclass);
if (!empty($data['timesplitting'])) {
$timesplittingclass = \tool_analytics\output\helper::option_to_class($data['timesplitting']);
if (\core_analytics\manager::is_valid($timesplittingclass, '\core_analytics\local\time_splitting\base') === false) {
$errors['timesplitting'] = get_string('errorinvalidtimesplitting', 'analytics');
}
$timesplitting = \core_analytics\manager::get_time_splitting($timesplittingclass);
if (!$target->can_use_timesplitting($timesplitting)) {
$errors['timesplitting'] = get_string('invalidtimesplitting', 'tool_analytics');
}
}
if (!empty($data['contexts'])) {
$analyserclass = $target->get_analyser_class();
if (!$potentialcontexts = $analyserclass::potential_context_restrictions()) {
$errors['contexts'] = get_string('errornocontextrestrictions', 'analytics');
} else {
// Flip the contexts array so we can just diff by key.
$selectedcontexts = array_flip($data['contexts']);
$invalidcontexts = array_diff_key($selectedcontexts, $potentialcontexts);
if (!empty($invalidcontexts)) {
$errors['contexts'] = get_string('errorinvalidcontexts', 'analytics');
}
}
}
if (!$this->_customdata['staticmodel']) {
if (empty($data['indicators'])) {
$errors['indicators'] = get_string('errornoindicators', 'analytics');
} else {
foreach ($data['indicators'] as $indicator) {
$realindicatorname = \tool_analytics\output\helper::option_to_class($indicator);
if (\core_analytics\manager::is_valid($realindicatorname, '\core_analytics\local\indicator\base') === false) {
$errors['indicators'] = get_string('errorinvalidindicator', 'analytics', $realindicatorname);
}
}
}
}
if (!empty($data['enabled']) && empty($data['timesplitting'])) {
$errors['enabled'] = get_string('errorcantenablenotimesplitting', 'tool_analytics');
}
return $errors;
}
/**
* Load the currently selected context options.
*
* @return array
*/
protected function load_current_contexts() {
$contexts = [];
foreach ($this->_customdata['contexts'] as $context) {
$contexts[$context->id] = $context->get_context_name(true, true);
}
return $contexts;
}
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* Model upload form.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Model upload form.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_model extends \moodleform {
/**
* Form definition.
*
* @return null
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('header', 'settingsheader', get_string('importmodel', 'tool_analytics'));
$mform->addElement('filepicker', 'modelfile', get_string('file'), null, ['accepted_types' => '.zip']);
$mform->addRule('modelfile', null, 'required');
$mform->addElement('advcheckbox', 'ignoreversionmismatches', get_string('ignoreversionmismatches', 'tool_analytics'),
get_string('ignoreversionmismatchescheckbox', 'tool_analytics'));
$this->add_action_buttons(true, get_string('import'));
}
}
@@ -0,0 +1,158 @@
<?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/>.
/**
* Typical crappy helper class with tiny functions.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die();
/**
* Helper class with general purpose tiny functions.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Converts a class full name to a select option key
*
* @param string $class
* @return string
*/
public static function class_to_option($class) {
// Form field is PARAM_ALPHANUMEXT and we are sending fully qualified class names
// as option names, but replacing the backslash for a string that is really unlikely
// to ever be part of a class name.
return str_replace('\\', '__', $class ?? '');
}
/**
* option_to_class
*
* @param string $option
* @return string
*/
public static function option_to_class($option) {
// Really unlikely but yeah, I'm a bad booyyy.
return str_replace('__', '\\', $option);
}
/**
* Sets an analytics > analytics models > $title breadcrumb.
*
* @param string $title
* @param \moodle_url $url
* @param \context|null $context Defaults to context_system
* @return null
*/
public static function set_navbar(string $title, \moodle_url $url, ?\context $context = null) {
global $PAGE;
if (!$context) {
$context = \context_system::instance();
}
$PAGE->set_context($context);
$PAGE->set_url($url);
$PAGE->set_secondary_active_tab('siteadminnode');
$PAGE->set_primary_active_tab('siteadminnode');
if ($siteadmin = $PAGE->settingsnav->find('root', \navigation_node::TYPE_SITE_ADMIN)) {
$PAGE->navbar->add($siteadmin->get_content(), $siteadmin->action(),
\breadcrumb_navigation_node::TYPE_SITE_ADMIN, null, 'root');
}
if ($analytics = $PAGE->settingsnav->find('analytics', \navigation_node::TYPE_SETTING)) {
$PAGE->navbar->add($analytics->get_content(), $analytics->action());
}
if ($analyticmodels = $PAGE->settingsnav->find('analyticmodels', \navigation_node::TYPE_SETTING)) {
$PAGE->navbar->add($analyticmodels->get_content(), $analyticmodels->action());
}
$PAGE->navbar->add($title);
$PAGE->set_pagelayout('report');
$PAGE->set_title($title);
$PAGE->set_heading($title);
}
/**
* Resets the current page.
*
* Note that this function can only be used by analytics pages that work at the system context.
*
* @return null
*/
public static function reset_page() {
global $PAGE;
$PAGE->reset_theme_and_output();
$PAGE->set_context(\context_system::instance());
}
/**
* Convert a list of contexts to an associative array where the value is the context name.
*
* @param array $contexts
* @param \moodle_url $url
* @param \renderer_base $output
* @param int|null $selected
* @param bool $includeall
* @param bool $shortentext
* @return \stdClass
*/
public static function prediction_context_selector(array $contexts, \moodle_url $url, \renderer_base $output,
?int $selected = null, ?bool $includeall = false, ?bool $shortentext = true): \stdClass {
foreach ($contexts as $contextid => $unused) {
// We prepare this to be used as single_select template options.
$context = \context::instance_by_id($contextid);
// Special name for system level predictions as showing "System is not visually nice".
if ($contextid == SYSCONTEXTID) {
$contextname = get_string('allpredictions', 'tool_analytics');
} else {
if ($shortentext) {
$contextname = shorten_text($context->get_context_name(false, true), 40);
} else {
$contextname = $context->get_context_name(false, true);
}
}
$contexts[$contextid] = $contextname;
}
if ($includeall) {
$contexts[0] = get_string('all');
$nothing = '';
} else {
$nothing = array('' => 'choosedots');
}
\core_collator::asort($contexts);
if (!$selected) {
$selected = '';
}
$singleselect = new \single_select($url, 'contextid', $contexts, $selected, $nothing);
return $singleselect->export_for_template($output);
}
}
@@ -0,0 +1,184 @@
<?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/>.
/**
* Insights report renderable.
*
* @package tool_analytics
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die;
/**
* Insights report renderable.
*
* @package tool_analytics
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class insights_report implements \renderable, \templatable {
/**
* @var \core_analytics\model
*/
private $model = null;
/**
* @var \context
*/
private $context = null;
/**
* Inits the insights report renderable.
*
* @param \core_analytics\model $model
* @param int|null $contextid
* @return null
*/
public function __construct(\core_analytics\model $model, ?int $contextid = null) {
$this->model = $model;
if ($contextid) {
$this->context = \context::instance_by_id($contextid);
}
}
/**
* Export the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output): \stdClass {
// Prepare the context object.
$data = new \stdClass();
$data->modelname = $this->model->get_name();
$data->charts = [];
$predictionactionrecords = $this->model->get_prediction_actions($this->context);
// Context selector.
$predictioncontexts = $this->model->get_predictions_contexts(false);
if ($predictioncontexts && count($predictioncontexts) > 1) {
$url = new \moodle_url('/admin/tool/analytics/model.php', ['id' => $this->model->get_id(),
'action' => 'insightsreport']);
if ($this->context) {
$selected = $this->context->id;
} else {
// This is the 'all' option.
$selected = 0;
}
$data->contextselect = \tool_analytics\output\helper::prediction_context_selector($predictioncontexts,
$url, $output, $selected, true, false);
}
if ($predictionactionrecords->valid()) {
foreach ($predictionactionrecords as $record) {
// Using this unusual execution flow to init the chart data because $predictionactionrecords
// is a \moodle_recordset.
if (empty($actionlabels)) {
list($actionlabels, $actionvalues, $actiontypes) = $this->init_action_labels($record);
}
// One value for each action.
$actionvalues['separated'][$record->actionname]++;
// Grouped value.
$actiontype = $actiontypes[$record->actionname];
$actionvalues['grouped'][$actiontype]++;
}
$predictionactionrecords->close();
// Actions doughtnut.
$chart = new \core\chart_pie();
$chart->set_doughnut(true);
$chart->set_title(get_string('actionsexecutedbyusers', 'tool_analytics'));
$series = new \core\chart_series(get_string('actions', 'tool_analytics'),
array_values($actionvalues['separated']));
$chart->add_series($series);
$chart->set_labels(array_values($actionlabels['separated']));
$data->separatedchart = $output->render($chart);
// Positive/negative/neutral bar chart.
$chart = new \core\chart_bar();
$chart->set_title(get_string('actionexecutedgroupedusefulness', 'tool_analytics'));
$series = new \core\chart_series(get_string('actions', 'tool_analytics'),
array_values($actionvalues['grouped']));
$chart->add_series($series);
$chart->set_labels(array_values($actionlabels['grouped']));
$data->groupedchart = $output->render($chart);
} else {
$predictionactionrecords->close();
$data->noactions = [
'message' => get_string('noactionsfound', 'tool_analytics'),
'announce' => true,
];
}
return $data;
}
/**
* Initialises the action labels and values in this model.
*
* @param \stdClass $predictionactionrecord
* @return array Two-dimensional array with the labels and values initialised to zero.
*/
private function init_action_labels(\stdClass $predictionactionrecord): array {
$predictioncontext = \context::instance_by_id($predictionactionrecord->contextid);
// Just 1 result, we just want to retrieve the prediction action names.
list ($unused, $predictions) = $this->model->get_predictions($predictioncontext, false, 0, 1);
// We pass 'true' for $isinsightuser so all the prediction actions available for this target are returning.
$predictionactions = $this->model->get_target()->prediction_actions(reset($predictions), true, true);
$actionlabels = [];
$actionvalues = ['separated' => [], 'grouped' => []];
$actiontypes = [];
foreach ($predictionactions as $action) {
$actionlabels['separated'][$action->get_action_name()] = $action->get_text();
$actionvalues['separated'][$action->get_action_name()] = 0;
$actiontypes[$action->get_action_name()] = $action->get_type();
}
$bulkactions = $this->model->get_target()->bulk_actions($predictions);
foreach ($bulkactions as $action) {
$actionlabels['separated'][$action->get_action_name()] = $action->get_text();
$actionvalues['separated'][$action->get_action_name()] = 0;
$actiontypes[$action->get_action_name()] = $action->get_type();
}
$actionlabels['grouped'][\core_analytics\action::TYPE_POSITIVE] = get_string('useful', 'analytics');
$actionlabels['grouped'][\core_analytics\action::TYPE_NEUTRAL] = get_string('neutral', 'analytics');
$actionlabels['grouped'][\core_analytics\action::TYPE_NEGATIVE] = get_string('notuseful', 'analytics');
$actionvalues['grouped'][\core_analytics\action::TYPE_POSITIVE] = 0;
$actionvalues['grouped'][\core_analytics\action::TYPE_NEUTRAL] = 0;
$actionvalues['grouped'][\core_analytics\action::TYPE_NEGATIVE] = 0;
return [$actionlabels, $actionvalues, $actiontypes];
}
}
@@ -0,0 +1,167 @@
<?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/>.
/**
* Invalid analysables renderable.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die;
/**
* Invalid analysables renderable.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class invalid_analysables implements \renderable, \templatable {
/**
* @var \core_analytics\model
*/
protected $model = null;
/**
* @var int
*/
protected $page = 0;
/**
* @var int
*/
protected $perpage = 0;
/**
* Inits the invalid analysables renderable.
*
* @param \core_analytics\model $model
* @param int $page
* @param int $perpage
* @return \stdClass
*/
public function __construct(\core_analytics\model $model, $page, $perpage) {
$this->model = $model;
$this->page = $page;
$this->perpage = $perpage;
}
/**
* Export the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$offset = $this->page * $this->perpage;
$contexts = $this->model->get_contexts();
$analysables = $this->model->get_analyser(['notimesplitting' => true])->get_analysables_iterator(null, $contexts);
$skipped = 0;
$enoughresults = false;
$morepages = false;
$results = array();
foreach ($analysables as $analysable) {
if (!$analysable) {
continue;
}
$validtraining = $this->model->get_target()->is_valid_analysable($analysable, true);
if ($validtraining === true) {
if ($this->model->is_static()) {
// We still want to show this analysable if it is not valid to get predictions.
$validtraining = get_string('notrainingbasedassumptions', 'analytics');
} else {
// We skip analysables that are valid for training or valid for prediction.
continue;
}
}
$validprediction = $this->model->get_target()->is_valid_analysable($analysable, false);
if ($validprediction === true) {
// We skip analysables that are valid for training or valid for prediction.
continue;
}
if ($offset && $skipped < $offset) {
$skipped++;
continue;
}
// Add a new results if we don't have enough yet.
if (!$enoughresults) {
$results[$analysable->get_id()] = array($analysable, $validtraining, $validprediction);
if ($this->perpage && count($results) === $this->perpage) {
$enoughresults = true;
}
} else {
// Confirmed that we have results we can not fit into this page.
$morepages = true;
break;
}
}
// Prepare the context object.
$data = new \stdClass();
$data->modelname = $this->model->get_name();
if ($this->page > 0) {
$prev = clone $PAGE->url;
$prev->param('page', $this->page - 1);
$button = new \single_button($prev, get_string('previouspage', 'tool_analytics'), 'get');
$data->prev = $button->export_for_template($output);
}
if ($morepages) {
$next = clone $PAGE->url;
$next->param('page', $this->page + 1);
$button = new \single_button($next, get_string('nextpage', 'tool_analytics'), 'get');
$data->next = $button->export_for_template($output);
}
$data->analysables = [];
foreach ($results as list($analysable, $validtraining, $validprediction)) {
$obj = new \stdClass();
$obj->url = \html_writer::link($analysable->get_context()->get_url(), $analysable->get_name(),
array('target' => '_blank'));
if ($validtraining !== true) {
$obj->validtraining = $validtraining;
}
if ($validprediction !== true) {
$obj->validprediction = $validprediction;
}
$data->analysables[] = $obj;
}
if (empty($data->analysables)) {
$data->noanalysables = [
'message' => get_string('noinvalidanalysables', 'tool_analytics'),
'announce' => true,
];
}
return $data;
}
}
@@ -0,0 +1,217 @@
<?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/>.
/**
* Model logs table class.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/tablelib.php');
/**
* Model logs table class.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class model_logs extends \table_sql {
/**
* @var \core_analytics\model
*/
protected $model = null;
/**
* @var string|false
*/
protected $evaluationmode = false;
/**
* Sets up the table_log parameters.
*
* @param string $uniqueid unique id of form.
* @param \core_analytics\model $model
*/
public function __construct($uniqueid, $model) {
global $PAGE;
parent::__construct($uniqueid);
$this->model = $model;
$this->set_attribute('class', 'modellog generaltable generalbox');
$this->set_attribute('aria-live', 'polite');
$this->define_columns(array('time', 'version', 'evaluationmode', 'indicators', 'timesplitting',
'accuracy', 'info', 'usermodified'));
$this->define_headers(array(
get_string('time'),
get_string('version'),
get_string('evaluationmode', 'tool_analytics'),
get_string('indicators', 'tool_analytics'),
get_string('timesplittingmethod', 'analytics'),
get_string('accuracy', 'tool_analytics'),
get_string('info', 'tool_analytics'),
get_string('fullnameuser'),
));
$evaluationmodehelp = new \help_icon('evaluationmode', 'tool_analytics');
$this->define_help_for_headers([null, null, $evaluationmodehelp, null, null, null, null, null]);
$this->pageable(true);
$this->collapsible(false);
$this->sortable(false);
$this->is_downloadable(false);
$this->evaluationmode = optional_param('evaluationmode', false, PARAM_ALPHANUM);
if ($this->evaluationmode && $this->evaluationmode != 'configuration' && $this->evaluationmode != 'trainedmodel') {
$this->evaluationmode = '';
}
$this->define_baseurl($PAGE->url);
}
/**
* Generate the version column.
*
* @param \stdClass $log log data.
* @return string HTML for the version column
*/
public function col_version($log) {
$recenttimestr = get_string('strftimerecent', 'core_langconfig');
return userdate($log->version, $recenttimestr);
}
/**
* Generate the evaluation mode column.
*
* @param \stdClass $log log data.
* @return string HTML for the evaluationmode column
*/
public function col_evaluationmode($log) {
return get_string('evaluationmodecol' . $log->evaluationmode, 'tool_analytics');
}
/**
* Generate the time column.
*
* @param \stdClass $log log data.
* @return string HTML for the time column
*/
public function col_time($log) {
$recenttimestr = get_string('strftimerecent', 'core_langconfig');
return userdate($log->timecreated, $recenttimestr);
}
/**
* Generate the indicators column.
*
* @param \stdClass $log log data.
* @return string HTML for the indicators column
*/
public function col_indicators($log) {
$indicatorclasses = json_decode($log->indicators);
$indicators = array();
foreach ($indicatorclasses as $indicatorclass) {
$indicator = \core_analytics\manager::get_indicator($indicatorclass);
if ($indicator) {
$indicators[] = $indicator->get_name();
} else {
debugging('Can\'t load ' . $indicatorclass . ' indicator', DEBUG_DEVELOPER);
}
}
return '<ul><li>' . implode('</li><li>', $indicators) . '</li></ul>';
}
/**
* Generate the context column.
*
* @param \stdClass $log log data.
* @return string HTML for the context column
*/
public function col_timesplitting($log) {
$timesplitting = \core_analytics\manager::get_time_splitting($log->timesplitting);
return $timesplitting->get_name();
}
/**
* Generate the accuracy column.
*
* @param \stdClass $log log data.
* @return string HTML for the accuracy column
*/
public function col_accuracy($log) {
return strval(round($log->score * 100, 2)) . '%';
}
/**
* Generate the info column.
*
* @param \stdClass $log log data.
* @return string HTML for the score column
*/
public function col_info($log) {
global $PAGE;
if (empty($log->info) && empty($log->dir)) {
return '';
}
$info = array();
if (!empty($log->info)) {
$info = json_decode($log->info);
}
if (!empty($log->dir)) {
$info[] = get_string('predictorresultsin', 'tool_analytics', $log->dir);
}
$PAGE->requires->js_call_amd('tool_analytics/log_info', 'loadInfo', array($log->id, $info));
return \html_writer::link('#', get_string('view'), array('data-model-log-id' => $log->id));
}
/**
* Generate the usermodified column.
*
* @param \stdClass $log log data.
* @return string HTML for the usermodified column
*/
public function col_usermodified($log) {
$user = \core_user::get_user($log->usermodified);
return fullname($user);
}
/**
* Query the logs table. Store results in the object for use by build_table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
$total = count($this->model->get_logs());
$this->pagesize($pagesize, $total);
$this->rawdata = $this->model->get_logs($this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
}
@@ -0,0 +1,387 @@
<?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/>.
/**
* Prediction models list page.
*
* @package tool_analytics
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die();
/**
* Shows tool_analytics models list.
*
* @package tool_analytics
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class models_list implements \renderable, \templatable {
/**
* models
*
* @var \core_analytics\model[]
*/
protected $models = array();
/**
* __construct
*
* @param \core_analytics\model[] $models
* @return void
*/
public function __construct($models) {
$this->models = $models;
}
/**
* Exports the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$data = new \stdClass();
$newmodelmenu = new \action_menu();
$newmodelmenu->set_menu_trigger(get_string('newmodel', 'tool_analytics'), 'btn btn-secondary');
$newmodelmenu->set_menu_left();
$newmodelmenu->add(new \action_menu_link(
new \moodle_url('/admin/tool/analytics/createmodel.php'),
new \pix_icon('i/edit', ''),
get_string('createmodel', 'tool_analytics'),
false
));
$newmodelmenu->add(new \action_menu_link(
new \moodle_url('/admin/tool/analytics/importmodel.php'),
new \pix_icon('i/import', ''),
get_string('importmodel', 'tool_analytics'),
false
));
$newmodelmenu->add(new \action_menu_link(
new \moodle_url('/admin/tool/analytics/restoredefault.php'),
new \pix_icon('i/reload', ''),
get_string('restoredefault', 'tool_analytics'),
false
));
$data->newmodelmenu = $newmodelmenu->export_for_template($output);
$onlycli = get_config('analytics', 'onlycli');
if ($onlycli === false) {
// Default applied if no config found.
$onlycli = 1;
}
// Evaluation options.
$timesplittingsforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation(true);
$misconfiguredmodels = [];
$data->models = array();
foreach ($this->models as $model) {
$modeldata = $model->export($output);
// Check if there is a help icon for the target to show.
$identifier = $modeldata->target->get_identifier();
$component = $modeldata->target->get_component();
if (get_string_manager()->string_exists($identifier . '_help', $component)) {
$helpicon = new \help_icon($identifier, $component);
$modeldata->targethelp = $helpicon->export_for_template($output);
} else {
// We really want to encourage developers to add help to their targets.
debugging("The target '{$modeldata->target}' should include a '{$identifier}_help' string to
describe its purpose.", DEBUG_DEVELOPER);
}
if ($model->invalid_timesplitting_selected()) {
$misconfiguredmodels[$model->get_id()] = $model->get_name();
}
// Check if there is a help icon for the indicators to show.
if (!empty($modeldata->indicators)) {
$indicators = array();
foreach ($modeldata->indicators as $ind) {
// Create the indicator with the details we want for the context.
$indicator = new \stdClass();
$indicator->name = $ind->out();
$identifier = $ind->get_identifier();
$component = $ind->get_component();
if (get_string_manager()->string_exists($identifier . '_help', $component)) {
$helpicon = new \help_icon($identifier, $component);
$indicator->help = $helpicon->export_for_template($output);
} else {
// We really want to encourage developers to add help to their indicators.
debugging("The indicator '{$ind}' should include a '{$identifier}_help' string to
describe its purpose.", DEBUG_DEVELOPER);
}
$indicators[] = $indicator;
}
$modeldata->indicators = $indicators;
}
$modeldata->indicatorsnum = count($modeldata->indicators);
// Check if there is a help icon for the time splitting method.
if (!empty($modeldata->timesplitting)) {
$identifier = $modeldata->timesplitting->get_identifier();
$component = $modeldata->timesplitting->get_component();
if (get_string_manager()->string_exists($identifier . '_help', $component)) {
$helpicon = new \help_icon($identifier, $component);
$modeldata->timesplittinghelp = $helpicon->export_for_template($output);
} else {
// We really want to encourage developers to add help to their time splitting methods.
debugging("The analysis interval '{$modeldata->timesplitting}' should include a '{$identifier}_help'
string to describe its purpose.", DEBUG_DEVELOPER);
}
} else {
$helpicon = new \help_icon('timesplittingnotdefined', 'tool_analytics');
$modeldata->timesplittinghelp = $helpicon->export_for_template($output);
}
// Has this model generated predictions?.
$predictioncontexts = $model->get_predictions_contexts();
$anypredictionobtained = $model->any_prediction_obtained();
// Model predictions list.
if (!$model->is_enabled()) {
$modeldata->noinsights = get_string('disabledmodel', 'analytics');
} else if ($model->uses_insights()) {
if ($predictioncontexts) {
$url = new \moodle_url('/report/insights/insights.php', array('modelid' => $model->get_id()));
$modeldata->insights = \tool_analytics\output\helper::prediction_context_selector($predictioncontexts,
$url, $output);
}
if (empty($modeldata->insights)) {
if ($anypredictionobtained) {
$modeldata->noinsights = get_string('noinsights', 'analytics');
} else {
$modeldata->noinsights = get_string('nopredictionsyet', 'analytics');
}
}
} else {
$modeldata->noinsights = get_string('noinsightsmodel', 'analytics');
}
// Actions.
$actionsmenu = new \action_menu();
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('model-actions-' . $model->get_id());
$urlparams = ['id' => $model->get_id(), 'sesskey' => sesskey()];
// Get predictions.
if (!$onlycli && $modeldata->enabled && !empty($modeldata->timesplitting)) {
$urlparams['action'] = 'scheduledanalysis';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url,
new \pix_icon('i/notifications', get_string('executescheduledanalysis', 'tool_analytics')),
get_string('executescheduledanalysis', 'tool_analytics'));
$actionsmenu->add($icon);
}
// Evaluate machine-learning-based models.
if (!$onlycli && $model->get_indicators() && !$model->is_static()) {
// Extra is_trained call as trained_locally returns false if the model has not been trained yet.
$trainedonlyexternally = !$model->trained_locally() && $model->is_trained();
$actionid = 'evaluate-' . $model->get_id();
// Evaluation options.
$modeltimesplittingmethods = $this->timesplittings_options_for_evaluation($model, $timesplittingsforevaluation);
// Include the current time-splitting method as the default selection method the model already have one.
if ($model->get_model_obj()->timesplitting) {
$currenttimesplitting = ['id' => 'current', 'text' => get_string('currenttimesplitting', 'tool_analytics')];
array_unshift($modeltimesplittingmethods, $currenttimesplitting);
}
$evaluateparams = [$actionid, $trainedonlyexternally];
$PAGE->requires->js_call_amd('tool_analytics/model', 'selectEvaluationOptions', $evaluateparams);
$urlparams['action'] = 'evaluate';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/calc', get_string('evaluate', 'tool_analytics')),
get_string('evaluate', 'tool_analytics'), ['data-action-id' => $actionid,
'data-timesplitting-methods' => json_encode($modeltimesplittingmethods)]);
$actionsmenu->add($icon);
}
// Machine-learning-based models evaluation log.
if (!$model->is_static() && $model->get_logs()) {
$urlparams['action'] = 'log';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/report', get_string('viewlog', 'tool_analytics')),
get_string('viewlog', 'tool_analytics'));
$actionsmenu->add($icon);
}
// Edit model.
$urlparams['action'] = 'edit';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('t/edit', get_string('edit')), get_string('edit'));
$actionsmenu->add($icon);
// Enable / disable.
if ($model->is_enabled() || !empty($modeldata->timesplitting)) {
// If there is no timesplitting method set, the model can not be enabled.
if ($model->is_enabled()) {
$action = 'disable';
$text = get_string('disable');
$icontype = 't/block';
} else {
$action = 'enable';
$text = get_string('enable');
$icontype = 'i/checked';
}
$urlparams['action'] = $action;
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
$actionsmenu->add($icon);
}
// Export.
if (!$model->is_static()) {
$fullysetup = $model->get_indicators() && !empty($modeldata->timesplitting);
$istrained = $model->is_trained();
if ($fullysetup || $istrained) {
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
// Clear the previous action param from the URL, we will set it in JS.
$url->remove_params('action');
$actionid = 'export-' . $model->get_id();
$PAGE->requires->js_call_amd('tool_analytics/model', 'selectExportOptions',
[$actionid, $istrained]);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
get_string('export', 'tool_analytics')), get_string('export', 'tool_analytics'),
['data-action-id' => $actionid]);
$actionsmenu->add($icon);
}
}
// Insights report.
if (!empty($anypredictionobtained) && $model->uses_insights()) {
$urlparams['action'] = 'insightsreport';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$pix = new \pix_icon('i/report', get_string('insightsreport', 'tool_analytics'));
$icon = new \action_menu_link_secondary($url, $pix, get_string('insightsreport', 'tool_analytics'));
$actionsmenu->add($icon);
}
// Invalid analysables.
$analyser = $model->get_analyser(['notimesplitting' => true]);
if (!$analyser instanceof \core_analytics\local\analyser\sitewide) {
$urlparams['action'] = 'invalidanalysables';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$pix = new \pix_icon('i/report', get_string('invalidanalysables', 'tool_analytics'));
$icon = new \action_menu_link_secondary($url, $pix, get_string('invalidanalysables', 'tool_analytics'));
$actionsmenu->add($icon);
}
// Clear model.
if (!empty($anypredictionobtained) || $model->is_trained()) {
$actionid = 'clear-' . $model->get_id();
$PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'clear']);
$urlparams['action'] = 'clear';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('e/cleanup_messy_code',
get_string('clearpredictions', 'tool_analytics')), get_string('clearpredictions', 'tool_analytics'),
['data-action-id' => $actionid]);
$actionsmenu->add($icon);
}
// Delete model.
$actionid = 'delete-' . $model->get_id();
$PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'delete']);
$urlparams['action'] = 'delete';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('t/delete',
get_string('delete', 'tool_analytics')), get_string('delete', 'tool_analytics'),
['data-action-id' => $actionid]);
$actionsmenu->add($icon);
$modeldata->actions = $actionsmenu->export_for_template($output);
$data->models[] = $modeldata;
}
$data->warnings = [];
$data->infos = [];
if (!$onlycli) {
$data->warnings[] = (object)array('message' => get_string('bettercli', 'tool_analytics'), 'closebutton' => true);
} else {
$url = new \moodle_url('/admin/settings.php', array('section' => 'analyticssettings'),
'id_s_analytics_onlycli');
$langstrid = 'clievaluationandpredictionsnoadmin';
if (is_siteadmin()) {
$langstrid = 'clievaluationandpredictions';
}
$data->infos[] = (object)array('message' => get_string($langstrid, 'tool_analytics', $url->out()),
'closebutton' => true);
}
if ($misconfiguredmodels) {
$warningstr = get_string('invalidtimesplittinginmodels', 'tool_analytics', implode(', ', $misconfiguredmodels));
$data->warnings[] = (object)array('message' => $warningstr, 'closebutton' => true);
}
return $data;
}
/**
* Returns the list of time splitting methods that are available for evaluation.
*
* @param \core_analytics\model $model
* @param array $timesplittingsforevaluation
* @return array
*/
private function timesplittings_options_for_evaluation(\core_analytics\model $model,
array $timesplittingsforevaluation): array {
$modeltimesplittingmethods = [
['id' => 'all', 'text' => get_string('alltimesplittingmethods', 'tool_analytics')],
];
$potentialtimesplittingmethods = $model->get_potential_timesplittings();
foreach ($timesplittingsforevaluation as $timesplitting) {
if (empty($potentialtimesplittingmethods[$timesplitting->get_id()])) {
// This time-splitting method can not be used for this model.
continue;
}
$modeltimesplittingmethods[] = [
'id' => \tool_analytics\output\helper::class_to_option($timesplitting->get_id()),
'text' => $timesplitting->get_name()->out(),
];
}
return $modeltimesplittingmethods;
}
}
@@ -0,0 +1,259 @@
<?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/>.
/**
* Renderer.
*
* @package tool_analytics
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
/**
* Renderer class.
*
* @package tool_analytics
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param \tool_analytics\output\models_list $modelslist
* @return string HTML
*/
protected function render_models_list(\tool_analytics\output\models_list $modelslist) {
$data = $modelslist->export_for_template($this);
return parent::render_from_template('tool_analytics/models_list', $data);
}
/**
* Renders a table.
*
* @param \table_sql $table
* @return string HTML
*/
public function render_table(\table_sql $table) {
ob_start();
$table->out(10, true);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Web interface evaluate results.
*
* @param \stdClass[] $results
* @param string[] $logs
* @return string HTML
*/
public function render_evaluate_results($results, $logs = array()) {
$output = '';
foreach ($results as $timesplittingid => $result) {
if (!CLI_SCRIPT) {
$output .= $this->output->box_start('generalbox mb-3');
}
// Check that the array key is a string, not all results depend on time splitting methods (e.g. general errors).
if (!is_numeric($timesplittingid)) {
$timesplitting = \core_analytics\manager::get_time_splitting($timesplittingid);
$langstrdata = (object)array('name' => $timesplitting->get_name(), 'id' => $timesplittingid);
if (CLI_SCRIPT) {
$output .= $this->output->heading(get_string('scheduledanalysisresultscli', 'tool_analytics', $langstrdata), 3);
} else {
$output .= $this->output->heading(get_string('scheduledanalysisresults', 'tool_analytics', $langstrdata), 3);
}
}
if ($result->status == 0) {
$output .= $this->output->notification(get_string('goodmodel', 'tool_analytics'),
\core\output\notification::NOTIFY_SUCCESS);
} else if ($result->status === \core_analytics\model::NO_DATASET) {
$output .= $this->output->notification(get_string('nodatatoevaluate', 'tool_analytics'),
\core\output\notification::NOTIFY_WARNING);
}
if (isset($result->score)) {
// Score.
$output .= $this->output->heading(get_string('accuracy', 'tool_analytics') . ': ' .
round(floatval($result->score), 4) * 100 . '%', 4);
}
if (!empty($result->info)) {
foreach ($result->info as $message) {
$output .= $this->output->notification($message, \core\output\notification::NOTIFY_WARNING);
}
}
if (!CLI_SCRIPT) {
$output .= $this->output->box_end();
}
}
// Info logged during evaluation.
if (!empty($logs) && debugging()) {
$output .= $this->output->heading(get_string('extrainfo', 'tool_analytics'), 3);
foreach ($logs as $log) {
$output .= $this->output->notification($log, \core\output\notification::NOTIFY_WARNING);
}
}
if (!CLI_SCRIPT) {
$output .= $this->output->single_button(new \moodle_url('/admin/tool/analytics/index.php'),
get_string('continue'), 'get');
}
return $output;
}
/**
* Web interface training & prediction results.
*
* @param \stdClass|false $trainresults
* @param string[] $trainlogs
* @param \stdClass|false $predictresults
* @param string[] $predictlogs
* @return string HTML
*/
public function render_get_predictions_results($trainresults = false, $trainlogs = array(), $predictresults = false, $predictlogs = array()) {
$output = '';
if ($trainresults || (!empty($trainlogs) && debugging())) {
$output .= $this->output->heading(get_string('trainingresults', 'tool_analytics'), 3);
}
if ($trainresults) {
if ($trainresults->status == 0) {
$output .= $this->output->notification(
get_string('trainingprocessfinished', 'tool_analytics'),
\core\output\notification::NOTIFY_SUCCESS);
} else if ($trainresults->status === \core_analytics\model::NO_DATASET ||
$trainresults->status === \core_analytics\model::NOT_ENOUGH_DATA) {
$output .= $this->output->notification(
get_string('nodatatotrain', 'tool_analytics'),
\core\output\notification::NOTIFY_WARNING);
} else {
$output .= $this->output->notification(
get_string('generalerror', 'tool_analytics', $trainresults->status),
\core\output\notification::NOTIFY_ERROR);
}
}
if (!empty($trainlogs) && debugging()) {
$output .= $this->output->heading(get_string('extrainfo', 'tool_analytics'), 4);
foreach ($trainlogs as $log) {
$output .= $this->output->notification($log, \core\output\notification::NOTIFY_WARNING);
}
}
if ($predictresults || (!empty($predictlogs) && debugging())) {
$output .= $this->output->heading(
get_string('predictionresults', 'tool_analytics'), 3, 'main mt-3');
}
if ($predictresults) {
if ($predictresults->status == 0) {
$output .= $this->output->notification(
get_string('predictionprocessfinished', 'tool_analytics'),
\core\output\notification::NOTIFY_SUCCESS);
} else if ($predictresults->status === \core_analytics\model::NO_DATASET ||
$predictresults->status === \core_analytics\model::NOT_ENOUGH_DATA) {
$output .= $this->output->notification(
get_string('nodatatopredict', 'tool_analytics'),
\core\output\notification::NOTIFY_WARNING);
} else {
$output .= $this->output->notification(
get_string('generalerror', 'tool_analytics', $predictresults->status),
\core\output\notification::NOTIFY_ERROR);
}
}
if (!empty($predictlogs) && debugging()) {
$output .= $this->output->heading(get_string('extrainfo', 'tool_analytics'), 4);
foreach ($predictlogs as $log) {
$output .= $this->output->notification($log, \core\output\notification::NOTIFY_WARNING);
}
}
if (!CLI_SCRIPT) {
$output .= $this->output->single_button(new \moodle_url('/admin/tool/analytics/index.php'),
get_string('continue'), 'get');
}
return $output;
}
/**
* Defer to template.
*
* @param \tool_analytics\output\insights_report $insightsreport
* @return string HTML
*/
protected function render_insights_report(\tool_analytics\output\insights_report $insightsreport): string {
$data = $insightsreport->export_for_template($this);
return parent::render_from_template('tool_analytics/insights_report', $data);
}
/**
* Defer to template.
*
* @param \tool_analytics\output\invalid_analysables $invalidanalysables
* @return string HTML
*/
protected function render_invalid_analysables(\tool_analytics\output\invalid_analysables $invalidanalysables) {
$data = $invalidanalysables->export_for_template($this);
return parent::render_from_template('tool_analytics/invalid_analysables', $data);
}
/**
* Renders an analytics disabled notification.
*
* @return string HTML
*/
public function render_analytics_disabled() {
global $FULLME;
$this->page->set_url($FULLME);
$this->page->set_title(get_string('pluginname', 'tool_analytics'));
$this->page->set_heading(get_string('pluginname', 'tool_analytics'));
$output = $this->output->header();
$output .= $this->output->notification(get_string('analyticsdisabled', 'analytics'),
\core\output\notification::NOTIFY_INFO);
$output .= \html_writer::tag('a', get_string('continue'), ['class' => 'btn btn-primary',
'href' => (new \moodle_url('/'))->out()]);
$output .= $this->output->footer();
return $output;
}
}
@@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - https://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/>.
/**
* Provides {@link \tool_analytics\output\restorable_models} class.
*
* @package tool_analytics
* @category output
* @copyright 2019 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\output;
defined('MOODLE_INTERNAL') || die();
/**
* Represents the list of default models that can be eventually restored.
*
* @copyright 2019 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restorable_models implements \renderable, \templatable {
/** @var array */
protected $models;
/**
* Instantiate an object of this class.
*
* @param array $models List of models as returned by {@link \core_analytics\manager::load_default_models_for_all_components()}
*/
public function __construct(array $models) {
$this->models = $models;
}
/**
* Export the list of models to be rendered.
*
* @param \renderer_base $output
* @return string
*/
public function export_for_template(\renderer_base $output) {
$components = [];
foreach ($this->models as $componentname => $modelslist) {
$component = [
'name' => $this->component_name($componentname),
'component' => $componentname,
'models' => [],
];
foreach ($modelslist as $definition) {
list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition);
if (\core_analytics\model::exists($target, $indicators)) {
continue;
}
$targetnamelangstring = $target->get_name();
$model = [
'defid' => \core_analytics\manager::model_declaration_identifier($definition),
'targetname' => $targetnamelangstring,
'targetclass' => $definition['target'],
'indicatorsnum' => count($definition['indicators']),
'indicators' => [],
];
if (get_string_manager()->string_exists($targetnamelangstring->get_identifier().'_help',
$targetnamelangstring->get_component())) {
$helpicon = new \help_icon($targetnamelangstring->get_identifier(), $targetnamelangstring->get_component());
$model['targethelp'] = $helpicon->export_for_template($output);
}
foreach ($indicators as $indicator) {
$indicatornamelangstring = $indicator->get_name();
$indicatordata = [
'name' => $indicatornamelangstring,
'classname' => $indicator->get_id(),
];
if (get_string_manager()->string_exists($indicatornamelangstring->get_identifier().'_help',
$indicatornamelangstring->get_component())) {
$helpicon = new \help_icon($indicatornamelangstring->get_identifier(),
$indicatornamelangstring->get_component());
$indicatordata['indicatorhelp'] = $helpicon->export_for_template($output);
}
$model['indicators'][] = $indicatordata;
}
$component['models'][] = $model;
}
if (!empty($component['models'])) {
$components[] = $component;
}
}
$result = [
'hasdata' => !empty($components),
'components' => array_values($components),
'submiturl' => new \moodle_url('/admin/tool/analytics/restoredefault.php'),
'backurl' => new \moodle_url('/admin/tool/analytics/index.php'),
'sesskey' => sesskey(),
];
return $result;
}
/**
* Return a human readable name for the given frankenstyle component.
*
* @param string $component Frankenstyle component such as 'core', 'core_analytics' or 'mod_workshop'
* @return string Human readable name of the component
*/
protected function component_name(string $component): string {
if ($component === 'core' || strpos($component, 'core_')) {
return get_string('componentcore', 'tool_analytics');
} else {
return get_string('pluginname', $component);
}
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for tool_analytics.
*
* @package tool_analytics
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for tool_analytics implementing null_provider.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,82 @@
<?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/>.
/**
* Predict system models with new data available.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\task;
defined('MOODLE_INTERNAL') || die();
/**
* Predict system models with new data available.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class predict_models extends \core\task\scheduled_task {
/**
* get_name
*
* @return string
*/
public function get_name() {
return get_string('predictmodels', 'tool_analytics');
}
/**
* Executes the prediction task.
*
* @return void
*/
public function execute() {
global $OUTPUT, $PAGE;
if (!\core_analytics\manager::is_analytics_enabled()) {
mtrace(get_string('analyticsdisabled', 'analytics'));
return;
}
$models = \core_analytics\manager::get_all_models(true, true);
if (!$models) {
mtrace(get_string('errornoenabledandtrainedmodels', 'tool_analytics'));
return;
}
foreach ($models as $model) {
$renderer = $PAGE->get_renderer('tool_analytics');
$result = $model->predict();
// Reset the page as some indicators may call external functions that overwrite the page context.
\tool_analytics\output\helper::reset_page();
if ($result) {
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
echo $renderer->render_get_predictions_results(false, array(), $result, $model->get_analyser()->get_logs());
}
}
}
}
@@ -0,0 +1,91 @@
<?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/>.
/**
* Train system models with new data available.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_analytics\task;
defined('MOODLE_INTERNAL') || die();
/**
* Train system models with new data available.
*
* @package tool_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class train_models extends \core\task\scheduled_task {
/**
* get_name
*
* @return string
*/
public function get_name() {
return get_string('trainmodels', 'tool_analytics');
}
/**
* Executes the prediction task.
*
* @return void
*/
public function execute() {
global $OUTPUT, $PAGE;
if (!\core_analytics\manager::is_analytics_enabled()) {
mtrace(get_string('analyticsdisabled', 'analytics'));
return;
}
$models = \core_analytics\manager::get_all_models(true);
if (!$models) {
mtrace(get_string('errornoenabledmodels', 'tool_analytics'));
return;
}
foreach ($models as $model) {
if ($model->is_static()) {
// Skip models based on assumptions.
continue;
}
if (!$model->get_time_splitting()) {
// Can not train if there is no time splitting method selected.
continue;
}
$renderer = $PAGE->get_renderer('tool_analytics');
$result = $model->train();
// Reset the page as some indicators may call external functions that overwrite the page context.
\tool_analytics\output\helper::reset_page();
if ($result) {
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
echo $renderer->render_get_predictions_results($result, $model->get_analyser()->get_logs());
}
}
}
}