admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
-auth/fc/fcFPP.php
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
filter/tex/mimetex.*
lib/editor/tinymce/plugins/pdw/tinymce/
lib/editor/tinymce/plugins/spellchecker/rpc.php
lib/editor/tinymce/tiny_mce/
+lib/mlbackend/php/phpml/
lib/adodb/
lib/bennu/
lib/evalmath/
lib/ltiprovider/
lib/amd/src/truncate.js
lib/fonts/
+lib/validateurlsyntax.php
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
-auth/fc/fcFPP.php
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
filter/tex/mimetex.*
lib/editor/tinymce/plugins/pdw/tinymce/
lib/editor/tinymce/plugins/spellchecker/rpc.php
lib/editor/tinymce/tiny_mce/
+lib/mlbackend/php/phpml/
lib/adodb/
lib/bennu/
lib/evalmath/
lib/ltiprovider/
lib/amd/src/truncate.js
lib/fonts/
+lib/validateurlsyntax.php
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
{
+ "plugins": [
+ "stylelint-csstree-validator"
+ ],
"rules": {
+ "csstree/validator": true,
"at-rule-empty-line-before": [ "always",
- {"except": [ "blockless-group"], ignore: ["after-comment", "all-nested"]}
+ {"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
],
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
"at-rule-no-unknown": null, # Enabled for non-scss in grunt.
"at-rule-semicolon-newline-after": "always",
+ "at-rule-semicolon-space-before": "never",
"block-closing-brace-newline-after": "always",
- "block-closing-brace-newline-before": "always-multi-line",
+ "block-closing-brace-newline-before": "always",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
- "block-no-single-line": true,
- "block-opening-brace-newline-after": "always-multi-line",
+ "block-opening-brace-newline-after": "always",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": ["lower", { "severity": "warning" }],
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": true,
- "declaration-block-no-ignored-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always-multi-line",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"declaration-no-important": true,
+ "font-family-no-duplicate-names": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
"function-comma-space-after": "always-single-line",
"function-name-case": "lower",
"function-parentheses-newline-inside": "always-multi-line",
"function-parentheses-space-inside": "never-single-line",
- "function-url-data-uris": never,
+ "function-url-scheme-blacklist": ["data"],
"function-whitespace-after": "always",
"indentation": 4,
"keyframe-declaration-no-important": true,
"max-line-length": [132, { "severity": "warning" }],
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
- "media-feature-no-missing-punctuation": true,
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always-multi-line",
"media-query-list-comma-space-after": "always-single-line",
"media-query-list-comma-space-before": "never",
- "no-browser-hacks": null, # Enabled for non-scss in grunt.
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": [true, { "severity": "warning" }],
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-no-unknown": true,
- "selector-root-no-composition": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"string-no-newline": true,
- "time-no-imperceptible": true,
+ "time-min-milliseconds": 100,
"unit-blacklist": ["pt"],
"unit-case": "lower",
"unit-no-unknown": true,
- $HOME/.npm
install:
- - sudo apt-get -y install haveged
- - sudo service haveged start
- >
if [ "$DB" = 'mysqli' ];
then
rules: {
// These rules have to be disabled in .stylelintrc for scss compat.
"at-rule-no-unknown": true,
- "no-browser-hacks": [true, {"severity": "warning"}]
}
}
},
rules: {
// These rules have to be disabled in .stylelintrc for scss compat.
"at-rule-no-unknown": true,
- "no-browser-hacks": [true, {"severity": "warning"}]
}
}
}
if (!$options['skip-database']) {
install_cli_database($options, $interactive);
+ // This needs to happen at the end to ensure it occurs after all caches
+ // have been purged for the last time.
+ // This will build a cached version of the current theme for the user
+ // to immediately start browsing the site.
+ require_once($CFG->libdir.'/upgradelib.php');
+ upgrade_themes();
} else {
echo get_string('cliskipdatabase', 'install')."\n";
}
install_cli_database($options, true);
+// This needs to happen at the end to ensure it occurs after all caches
+// have been purged for the last time.
+// This will build a cached version of the current theme for the user
+// to immediately start browsing the site.
+require_once($CFG->libdir.'/upgradelib.php');
+upgrade_themes();
+
echo get_string('cliinstallfinished', 'install')."\n";
exit(0); // 0 means success
admin_apply_default_settings(NULL, false);
admin_apply_default_settings(NULL, false);
+// This needs to happen at the end to ensure it occurs after all caches
+// have been purged for the last time.
+// This will build a cached version of the current theme for the user
+// to immediately start browsing the site.
+upgrade_themes();
+
echo get_string('cliupgradefinished', 'admin')."\n";
exit(0); // 0 means success
//remove moodle.org from the hub list
foreach ($hubs as $key => $hub) {
- if ($hub['url'] == HUB_MOODLEORGHUBURL) {
+ if ($hub['url'] == HUB_MOODLEORGHUBURL || $hub['url'] == HUB_OLDMOODLEORGHUBURL) {
unset($hubs[$key]);
}
}
$imageurl = get_config('hub', 'site_imageurl_' . $cleanhuburl);
$privacy = get_config('hub', 'site_privacy_' . $cleanhuburl);
$address = get_config('hub', 'site_address_' . $cleanhuburl);
+ if ($address === false) {
+ $address = '';
+ }
$region = get_config('hub', 'site_region_' . $cleanhuburl);
$country = get_config('hub', 'site_country_' . $cleanhuburl);
- if ($country === false) {
- $country = $admin->country;
+ if (empty($country)) {
+ $country = $admin->country ?: $CFG->country;
}
$language = get_config('hub', 'site_language_' . $cleanhuburl);
if ($language === false) {
$language = explode('_', current_language())[0];
}
$geolocation = get_config('hub', 'site_geolocation_' . $cleanhuburl);
+ if ($geolocation === false) {
+ $geolocation = '';
+ }
$contactable = get_config('hub', 'site_contactable_' . $cleanhuburl);
$emailalert = get_config('hub', 'site_emailalert_' . $cleanhuburl);
- $emailalert = ($emailalert === 0) ? 0 : 1;
+ $emailalert = ($emailalert === false || $emailalert) ? 1 : 0;
$coursesnumber = get_config('hub', 'site_coursesnumber_' . $cleanhuburl);
$usersnumber = get_config('hub', 'site_usersnumber_' . $cleanhuburl);
$roleassignmentsnumber = get_config('hub', 'site_roleassignmentsnumber_' . $cleanhuburl);
$mform->addElement('hidden', 'regioncode', '-');
$mform->setType('regioncode', PARAM_ALPHANUMEXT);
- $countries = get_string_manager()->get_list_of_countries();
+ $countries = ['' => ''] + get_string_manager()->get_list_of_countries();
$mform->addElement('select', 'countrycode', get_string('sitecountry', 'hub'), $countries);
$mform->setDefault('countrycode', $country);
$mform->setType('countrycode', PARAM_ALPHANUMEXT);
$mform->addHelpButton('countrycode', 'sitecountry', 'hub');
+ $mform->addRule('countrycode', $strrequired, 'required', null, 'client');
$mform->addElement('text', 'geolocation', get_string('sitegeolocation', 'hub'),
array('class' => 'registration_textfield'));
$mform->addElement('text', 'contactphone', get_string('sitephone', 'hub'),
array('class' => 'registration_textfield'));
$mform->setType('contactphone', PARAM_TEXT);
+ $mform->setDefault('contactphone', $contactphone);
$mform->addHelpButton('contactphone', 'sitephone', 'hub');
$mform->setForceLtr('contactphone');
echo $OUTPUT->header();
//check if the site is registered on Moodle.org and display a message about registering on MOOCH
- $registered = $DB->count_records('registration_hubs', array('huburl' => HUB_MOODLEORGHUBURL, 'confirmed' => 1));
- if (empty($registered)) {
- $warningmsg = get_string('registermoochtips', 'hub');
- $warningmsg .= $renderer->single_button(new moodle_url('register.php', array('huburl' => HUB_MOODLEORGHUBURL
- , 'hubname' => 'Moodle.org')), get_string('register', 'admin'));
- echo $renderer->box($warningmsg, 'buttons mdl-align generalbox adminwarning');
- }
+ $adminrenderer = $PAGE->get_renderer('core', 'admin');
+ echo $adminrenderer->warn_if_not_registered();
//do not check sesskey if confirm = false because this script is linked into email message
if (!empty($errormessage)) {
// Some Moodle.org registration explanation.
if ($huburl == HUB_MOODLEORGHUBURL) {
+ $notificationtype = \core\output\notification::NOTIFY_ERROR;
if (!empty($registeredhub->token)) {
if ($registeredhub->timemodified == 0) {
$registrationmessage = get_string('pleaserefreshregistrationunknown', 'admin');
} else {
$lastupdated = userdate($registeredhub->timemodified, get_string('strftimedate', 'langconfig'));
$registrationmessage = get_string('pleaserefreshregistration', 'admin', $lastupdated);
+ $notificationtype = \core\output\notification::NOTIFY_INFO;
}
} else {
$registrationmessage = get_string('registrationwarning', 'admin');
}
- echo $OUTPUT->notification($registrationmessage);
+ echo $OUTPUT->notification($registrationmessage, $notificationtype);
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
$renderer = $PAGE->get_renderer('core', 'register');
if (!$registered) {
- $registerbutton = $this->single_button(new moodle_url('/admin/registration/register.php',
- array('huburl' => HUB_MOODLEORGHUBURL, 'hubname' => 'Moodle.org')),
+ if (has_capability('moodle/site:config', context_system::instance())) {
+ $registerbutton = $this->single_button(new moodle_url('/admin/registration/register.php',
+ array('huburl' => HUB_MOODLEORGHUBURL, 'hubname' => 'Moodle.net')),
get_string('register', 'admin'));
+ $str = 'registrationwarning';
+ } else {
+ $registerbutton = '';
+ $str = 'registrationwarningcontactadmin';
+ }
- return $this->warning( get_string('registrationwarning', 'admin')
- . ' ' . $this->help_icon('registration', 'admin') . $registerbutton );
+ return $this->warning( get_string($str, 'admin')
+ . ' ' . $this->help_icon('registration', 'admin') . $registerbutton ,
+ 'error alert alert-danger');
}
return '';
}
+ /**
+ * Return an admin page warning if site is not registered with moodle.org
+ *
+ * @return string
+ */
+ public function warn_if_not_registered() {
+ global $CFG;
+ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
+ $registrationmanager = new registration_manager();
+ return $this->registration_warning($registrationmanager->get_registeredhub(HUB_MOODLEORGHUBURL) ? true : false);
+ }
+
/**
* Helper method to render the information about the available Moodle update
*
// to modify them
echo $OUTPUT->header($focus);
+// Display a warning if site is not registered.
+if (empty($query)) {
+ $adminrenderer = $PAGE->get_renderer('core', 'admin');
+ echo $adminrenderer->warn_if_not_registered();
+}
+
echo $OUTPUT->heading(get_string('administrationsite'));
if ($errormsg !== '') {
--- /dev/null
+<?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/>.
+
+/**
+ * Adds settings links to admin tree.
+ *
+ * @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
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+ $settings = new admin_settingpage('analyticssettings', new lang_string('analyticssettings', 'analytics'));
+ $ADMIN->add('appearance', $settings);
+
+ if ($ADMIN->fulltree) {
+ // Select the site prediction's processor.
+ $predictionprocessors = \core_analytics\manager::get_all_prediction_processors();
+ $predictors = array();
+ foreach ($predictionprocessors as $fullclassname => $predictor) {
+ $pluginname = substr($fullclassname, 1, strpos($fullclassname, '\\', 1) - 1);
+ $predictors[$fullclassname] = new lang_string('pluginname', $pluginname);
+ }
+ $settings->add(new \core_analytics\admin_setting_predictor('analytics/predictionsprocessor',
+ new lang_string('predictionsprocessor', 'analytics'), new lang_string('predictionsprocessor_help', 'analytics'),
+ '\mlbackend_php\processor', $predictors)
+ );
+
+ // Log store.
+ $logmanager = get_log_manager();
+ $readers = $logmanager->get_readers('core\log\sql_reader');
+ $options = array();
+ $defaultreader = null;
+ foreach ($readers as $plugin => $reader) {
+ if (!$reader->is_logging()) {
+ continue;
+ }
+ if (!isset($defaultreader)) {
+ // The top one as default reader.
+ $defaultreader = $plugin;
+ }
+ $options[$plugin] = $reader->get_name();
+ }
+
+ if (empty($defaultreader)) {
+ // We fall here during initial site installation because log stores are not
+ // enabled until admin/tool/log/db/install.php is executed and get_readers
+ // return nothing.
+
+ if ($enabledlogstores = get_config('tool_log', 'enabled_stores')) {
+ $enabledlogstores = explode(',', $enabledlogstores);
+ $defaultreader = reset($enabledlogstores);
+
+ // No need to set the correct name, just the value, this will not be displayed.
+ $options[$defaultreader] = $defaultreader;
+ }
+ }
+ $settings->add(new admin_setting_configselect('analytics/logstore',
+ new lang_string('analyticslogstore', 'analytics'), new lang_string('analyticslogstore_help', 'analytics'),
+ $defaultreader, $options));
+
+ // Enable/disable time splitting methods.
+ $alltimesplittings = \core_analytics\manager::get_all_time_splittings();
+
+ $timesplittingoptions = array();
+ $timesplittingdefaults = array('\core\analytics\time_splitting\quarters_accum',
+ '\core\analytics\time_splitting\quarters', '\core\analytics\time_splitting\no_splitting');
+ foreach ($alltimesplittings as $key => $timesplitting) {
+ $timesplittingoptions[$key] = $timesplitting->get_name();
+ }
+ $settings->add(new admin_setting_configmultiselect('analytics/timesplittings',
+ new lang_string('enabledtimesplittings', 'analytics'), new lang_string('enabledtimesplittings_help', 'analytics'),
+ $timesplittingdefaults, $timesplittingoptions)
+ );
+
+ // Predictions processor output dir.
+ $defaultmodeloutputdir = rtrim($CFG->dataroot, '/') . DIRECTORY_SEPARATOR . 'models';
+ if (empty(get_config('analytics', 'modeloutputdir')) && !file_exists($defaultmodeloutputdir) &&
+ is_writable($defaultmodeloutputdir)) {
+ // Automatically create the dir for them so users don't see the invalid value red cross.
+ mkdir($defaultmodeloutputdir, $CFG->directorypermissions, true);
+ }
+ $settings->add(new admin_setting_configdirectory('analytics/modeloutputdir', new lang_string('modeloutputdir', 'analytics'),
+ new lang_string('modeloutputdirinfo', 'analytics'), $defaultmodeloutputdir));
+ }
+}
new lang_string('passwordchangetokendeletion', 'admin'),
new lang_string('passwordchangetokendeletion_desc', 'admin'), 0));
+ $temp->add(new admin_setting_configduration('tokenduration',
+ new lang_string('tokenduration', 'admin'),
+ new lang_string('tokenduration_desc', 'admin'), 12 * WEEKSECS, WEEKSECS));
+
$temp->add(new admin_setting_configcheckbox('groupenrolmentkeypolicy', new lang_string('groupenrolmentkeypolicy', 'admin'), new lang_string('groupenrolmentkeypolicy_desc', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('disableuserimages', new lang_string('disableuserimages', 'admin'), new lang_string('configdisableuserimages', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('emailchangeconfirmation', new lang_string('emailchangeconfirmation', 'admin'), new lang_string('configemailchangeconfirmation', 'admin'), 1));
$ADMIN->add('root', new admin_externalpage('adminnotifications', new lang_string('notifications'), "$CFG->wwwroot/$CFG->admin/index.php"));
$ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_string('registration', 'admin'),
- "$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org&sesskey=" . sesskey()));
+ "$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.net&sesskey=" . sesskey()));
$ADMIN->add('root', new admin_externalpage('registrationhub', new lang_string('registerwith', 'hub'),
"$CFG->wwwroot/$CFG->admin/registration/register.php", 'moodle/site:config', true));
$ADMIN->add('root', new admin_externalpage('registrationhubs', new lang_string('hubs', 'admin'),
'phone2' => new lang_string('phone2'),
'department' => new lang_string('department'),
'institution' => new lang_string('institution'),
+ 'city' => new lang_string('city'),
+ 'country' => new lang_string('country'),
)));
$setting = new admin_setting_configtext('fullnamedisplay', new lang_string('fullnamedisplay', 'admin'),
new lang_string('configfullnamedisplay', 'admin'), 'language', PARAM_TEXT, 50);
--- /dev/null
+@core @core_admin
+Feature: Manage tokens
+ In order to manage webservice usage
+ As an admin
+ I need to be able to create and delete tokens
+
+ Background:
+ Given the following "users" exist:
+ | username | password | firstname | lastname |
+ | testuser | testuser | Joe | Bloggs |
+ | testuser2 | testuser2 | TestFirstname | TestLastname |
+ And I log in as "admin"
+ And I am on site homepage
+
+ @javascript
+ Scenario: Add & delete a token
+ Given I navigate to "Plugins > Web services > Manage tokens" in site administration
+ And I follow "Add"
+ And I set the field "User" to "Joe Bloggs"
+ And I set the field "IP restriction" to "127.0.0.1"
+ When I press "Save changes"
+ Then I should see "Joe Bloggs"
+ And I should see "127.0.0.1"
+ And I follow "Delete"
+ And I press "Delete"
+ And I should not see "Joe Bloggs"
--- /dev/null
+// 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/>.
+
+/**
+ * Shows a dialogue with info about this logs.
+ *
+ * @module tool_analytics/log_info
+ * @class log_info
+ * @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
+ */
+define(['jquery', 'core/str', 'core/modal_factory', 'core/notification'], function($, str, ModalFactory, Notification) {
+
+ return /** @alias module:tool_analytics/log_info */ {
+
+ /**
+ * Prepares a modal info for a log's results.
+ *
+ * @method loadInfo
+ * @param {int} id
+ * @param {string[]} info
+ */
+ loadInfo: function(id, info) {
+
+ var link = $('[data-model-log-id="' + id + '"]');
+ str.get_string('loginfo', 'tool_analytics').then(function(langString) {
+
+ var bodyInfo = $("<ul>");
+ info.forEach(function(item) {
+ bodyInfo.append('<li>' + item + '</li>');
+ });
+ bodyInfo.append("</ul>");
+
+ return ModalFactory.create({
+ title: langString,
+ body: bodyInfo.html(),
+ large: true,
+ }, link);
+
+ }).catch(Notification.exception);
+ }
+ };
+});
--- /dev/null
+<?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['model']->get_model_obj()->trained == 1) {
+ $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'));
+
+ $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);
+
+ $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');
+
+ $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);
+
+ if (!empty($data['timesplitting'])) {
+ $realtimesplitting = \tool_analytics\output\helper::option_to_class($data['timesplitting']);
+ if (\core_analytics\manager::is_valid($realtimesplitting, '\core_analytics\local\time_splitting\base') === false) {
+ $errors['timesplitting'] = get_string('errorinvalidtimesplitting', 'analytics');
+ }
+ }
+
+ 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;
+ }
+}
--- /dev/null
+<?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('\\', '2015102400ouuu', $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('2015102400ouuu', '\\', $option);
+ }
+}
--- /dev/null
+<?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;
+
+ /**
+ * 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', 'indicators', 'timesplitting', 'accuracy', 'info', 'usermodified'));
+ $this->define_headers(array(
+ get_string('time'),
+ get_string('version'),
+ get_string('indicators', 'tool_analytics'),
+ get_string('timesplittingmethod', 'analytics'),
+ get_string('accuracy', 'tool_analytics'),
+ get_string('info', 'tool_analytics'),
+ get_string('fullnameuser'),
+ ));
+ $this->pageable(true);
+ $this->collapsible(false);
+ $this->sortable(false);
+ $this->is_downloadable(false);
+
+ $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 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);
+ }
+ }
+}
--- /dev/null
+<?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) {
+
+ $data = new \stdClass();
+
+ $data->models = array();
+ foreach ($this->models as $model) {
+ $modeldata = $model->export();
+
+ // Model predictions list.
+ if ($model->uses_insights()) {
+ $predictioncontexts = $model->get_predictions_contexts();
+ if ($predictioncontexts) {
+
+ foreach ($predictioncontexts 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 {
+ $contextname = shorten_text($context->get_context_name(true, true), 90);
+ }
+ $predictioncontexts[$contextid] = $contextname;
+ }
+ \core_collator::asort($predictioncontexts);
+
+ if (!empty($predictioncontexts)) {
+ $url = new \moodle_url('/report/insights/insights.php', array('modelid' => $model->get_id()));
+ $singleselect = new \single_select($url, 'contextid', $predictioncontexts);
+ $modeldata->insights = $singleselect->export_for_template($output);
+ }
+ }
+
+ if (empty($modeldata->insights)) {
+ if ($model->any_prediction_obtained()) {
+ $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());
+ $actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
+
+ // Edit model.
+ if (!$model->is_static()) {
+ $url = new \moodle_url('model.php', array('action' => 'edit', 'id' => $model->get_id()));
+ $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()) {
+ $action = 'disable';
+ $text = get_string('disable');
+ $icontype = 't/block';
+ } else {
+ $action = 'enable';
+ $text = get_string('enable');
+ $icontype = 'i/checked';
+ }
+ $url = new \moodle_url('model.php', array('action' => $action, 'id' => $model->get_id()));
+ $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
+ $actionsmenu->add($icon);
+
+ // Evaluate machine-learning-based models.
+ if ($model->get_indicators() && !$model->is_static()) {
+ $url = new \moodle_url('model.php', array('action' => 'evaluate', 'id' => $model->get_id()));
+ $icon = new \action_menu_link_secondary($url, new \pix_icon('i/calc', get_string('evaluate', 'tool_analytics')),
+ get_string('evaluate', 'tool_analytics'));
+ $actionsmenu->add($icon);
+ }
+
+ if ($modeldata->enabled && !empty($modeldata->timesplitting)) {
+ $url = new \moodle_url('model.php', array('action' => 'getpredictions', 'id' => $model->get_id()));
+ $icon = new \action_menu_link_secondary($url, new \pix_icon('i/notifications',
+ get_string('getpredictions', 'tool_analytics')), get_string('getpredictions', 'tool_analytics'));
+ $actionsmenu->add($icon);
+ }
+
+ // Machine-learning-based models evaluation log.
+ if (!$model->is_static()) {
+ $url = new \moodle_url('model.php', array('action' => 'log', 'id' => $model->get_id()));
+ $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);
+ }
+
+ $modeldata->actions = $actionsmenu->export_for_template($output);
+
+ $data->models[] = $modeldata;
+ }
+
+ $data->warnings = array(
+ (object)array('message' => get_string('bettercli', 'tool_analytics'), 'closebutton' => true)
+ );
+
+ return $data;
+ }
+}
--- /dev/null
+<?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;
+use templatable;
+use renderable;
+
+/**
+ * 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()) {
+ global $OUTPUT;
+
+ $output = '';
+
+ foreach ($results as $timesplittingid => $result) {
+
+ if (!CLI_SCRIPT) {
+ $output .= $OUTPUT->box_start('generalbox m-b-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 .= $OUTPUT->heading(get_string('getpredictionsresultscli', 'tool_analytics', $langstrdata), 3);
+ } else {
+ $output .= $OUTPUT->heading(get_string('getpredictionsresults', 'tool_analytics', $langstrdata), 3);
+ }
+ }
+
+ if ($result->status == 0) {
+ $output .= $OUTPUT->notification(get_string('goodmodel', 'tool_analytics'),
+ \core\output\notification::NOTIFY_SUCCESS);
+ } else if ($result->status === \core_analytics\model::NO_DATASET) {
+ $output .= $OUTPUT->notification(get_string('nodatatoevaluate', 'tool_analytics'),
+ \core\output\notification::NOTIFY_WARNING);
+ }
+
+ if (isset($result->score)) {
+ // Score.
+ $output .= $OUTPUT->heading(get_string('accuracy', 'tool_analytics') . ': ' .
+ round(floatval($result->score), 4) * 100 . '%', 4);
+ }
+
+ if (!empty($result->info)) {
+ foreach ($result->info as $message) {
+ $output .= $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
+ }
+ }
+
+ if (!CLI_SCRIPT) {
+ $output .= $OUTPUT->box_end();
+ }
+ }
+
+ // Info logged during evaluation.
+ if (!empty($logs) && debugging()) {
+ $output .= $OUTPUT->heading(get_string('extrainfo', 'tool_analytics'), 3);
+ foreach ($logs as $log) {
+ $output .= $OUTPUT->notification($log, \core\output\notification::NOTIFY_WARNING);
+ }
+ }
+
+ if (!CLI_SCRIPT) {
+ $output .= $OUTPUT->single_button(new \moodle_url('/admin/tool/analytics/index.php'), get_string('continue'));
+ }
+
+ 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()) {
+ global $OUTPUT;
+
+ $output = '';
+
+ if ($trainresults || (!empty($trainlogs) && debugging())) {
+ $output .= $OUTPUT->heading(get_string('trainingresults', 'tool_analytics'), 3);
+ }
+
+ if ($trainresults) {
+ if ($trainresults->status == 0) {
+ $output .= $OUTPUT->notification(get_string('trainingprocessfinished', 'tool_analytics'),
+ \core\output\notification::NOTIFY_SUCCESS);
+ } else if ($trainresults->status === \core_analytics\model::NO_DATASET) {
+ $output .= $OUTPUT->notification(get_string('nodatatotrain', 'tool_analytics'),
+ \core\output\notification::NOTIFY_WARNING);
+ } else {
+ $output .= $OUTPUT->notification(get_string('generalerror', 'analytics', $trainresults->status),
+ \core\output\notification::NOTIFY_ERROR);
+ }
+ }
+
+ if (!empty($trainlogs) && debugging()) {
+ $output .= $OUTPUT->heading(get_string('extrainfo', 'tool_analytics'), 4);
+ foreach ($trainlogs as $log) {
+ $output .= $OUTPUT->notification($log, \core\output\notification::NOTIFY_WARNING);
+ }
+ }
+
+ if ($predictresults || (!empty($predictlogs) && debugging())) {
+ $output .= $OUTPUT->heading(get_string('predictionresults', 'tool_analytics'), 3, 'main m-t-3');
+ }
+
+ if ($predictresults) {
+ if ($predictresults->status == 0) {
+ $output .= $OUTPUT->notification(get_string('predictionprocessfinished', 'tool_analytics'),
+ \core\output\notification::NOTIFY_SUCCESS);
+ } else if ($predictresults->status === \core_analytics\model::NO_DATASET) {
+ $output .= $OUTPUT->notification(get_string('nodatatopredict', 'tool_analytics'),
+ \core\output\notification::NOTIFY_WARNING);
+ } else {
+ $output .= $OUTPUT->notification(get_string('generalerror', 'analytics', $predictresults->status),
+ \core\output\notification::NOTIFY_ERROR);
+ }
+ }
+
+ if (!empty($predictlogs) && debugging()) {
+ $output .= $OUTPUT->heading(get_string('extrainfo', 'tool_analytics'), 4);
+ foreach ($predictlogs as $log) {
+ $output .= $OUTPUT->notification($log, \core\output\notification::NOTIFY_WARNING);
+ }
+ }
+
+ if (!CLI_SCRIPT) {
+ $output .= $OUTPUT->single_button(new \moodle_url('/admin/tool/analytics/index.php'), get_string('continue'));
+ }
+
+ return $output;
+ }
+}
--- /dev/null
+<?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;
+
+ $models = \core_analytics\manager::get_all_models(true, true);
+ if (!$models) {
+ mtrace(get_string('errornoenabledandtrainedmodels', 'tool_analytics'));
+ return;
+ }
+
+ foreach ($models as $model) {
+ $result = $model->predict();
+ if ($result) {
+ echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_target()->get_name()));
+ $renderer = $PAGE->get_renderer('tool_analytics');
+ echo $renderer->render_get_predictions_results(false, array(), $result, $model->get_analyser()->get_logs());
+ }
+ }
+
+ }
+}
--- /dev/null
+<?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;
+
+ $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;
+ }
+
+ $result = $model->train();
+ if ($result) {
+ echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_target()->get_name()));
+
+ $renderer = $PAGE->get_renderer('tool_analytics');
+ echo $renderer->render_get_predictions_results($result, $model->get_analyser()->get_logs());
+ }
+ }
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Enables the provided model.
+ *
+ * @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
+ */
+
+define('CLI_SCRIPT', true);
+
+require_once(__DIR__ . '/../../../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+$help = "Enables the provided model.
+
+Options:
+--modelid Model id
+--timesplitting Time splitting method full class name
+-h, --help Print out this help
+
+Example:
+\$ php admin/tool/analytics/cli/enable_model.php --modelid=1 --timesplitting=\"\\core\\analytics\\time_splitting\\quarters\"
+";
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'modelid' => false,
+ 'timesplitting' => false
+ ),
+ array(
+ 'h' => 'help',
+ )
+);
+
+if ($options['help']) {
+ echo $help;
+ exit(0);
+}
+
+if ($options['modelid'] === false || $options['timesplitting'] === false) {
+ echo $help;
+ exit(0);
+}
+
+// We need admin permissions.
+\core\session\manager::set_user(get_admin());
+
+$model = new \core_analytics\model($options['modelid']);
+
+// Evaluate its suitability to predict accurately.
+$model->enable($options['timesplitting']);
+
+cli_heading(get_string('success'));
+exit(0);
--- /dev/null
+<?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/>.
+
+/**
+ * Evaluates the provided model.
+ *
+ * @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
+ */
+
+define('CLI_SCRIPT', true);
+
+require_once(__DIR__ . '/../../../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+$help = "Evaluates the provided model.
+
+Options:
+--modelid Model id
+--non-interactive Not interactive questions
+--timesplitting Restrict the evaluation to 1 single time splitting method (Optional)
+--filter Analyser dependant. e.g. A courseid would evaluate the model using a single course (Optional)
+--reuse-prev-analysed Reuse recently analysed courses instead of analysing the whole site. Set it to false while" .
+ " coding indicators. Defaults to true (Optional)" . "
+-h, --help Print out this help
+
+Example:
+\$ php admin/tool/analytics/cli/evaluate_model.php --modelid=1 --timesplitting='\\core\\analytics\\time_splitting\\quarters' --filter=123,321
+";
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'modelid' => false,
+ 'timesplitting' => false,
+ 'reuse-prev-analysed' => true,
+ 'non-interactive' => false,
+ 'filter' => false
+ ),
+ array(
+ 'h' => 'help',
+ )
+);
+
+if ($options['help']) {
+ echo $help;
+ exit(0);
+}
+
+if ($options['modelid'] === false) {
+ echo $help;
+ exit(0);
+}
+
+// Reformat them as an array.
+if ($options['filter'] !== false) {
+ $options['filter'] = explode(',', $options['filter']);
+}
+
+// We need admin permissions.
+\core\session\manager::set_user(get_admin());
+
+$model = new \core_analytics\model($options['modelid']);
+
+mtrace(get_string('analysingsitedata', 'tool_analytics'));
+
+if ($options['reuse-prev-analysed']) {
+ mtrace(get_string('evaluationinbatches', 'tool_analytics'));
+}
+
+$analyseroptions = array(
+ 'filter' => $options['filter'],
+ 'timesplitting' => $options['timesplitting'],
+ 'reuseprevanalysed' => $options['reuse-prev-analysed'],
+);
+// Evaluate its suitability to predict accurately.
+$results = $model->evaluate($analyseroptions);
+
+$renderer = $PAGE->get_renderer('tool_analytics');
+echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
+
+// Check that we have, at leasa,t 1 valid dataset (not necessarily good) to use.
+foreach ($results as $result) {
+ if ($result->status !== \core_analytics\model::NO_DATASET &&
+ $result->status !== \core_analytics\model::GENERAL_ERROR) {
+ $validdatasets = true;
+ }
+}
+
+if (!empty($validdatasets) && !$model->is_enabled() && $options['non-interactive'] === false) {
+
+ // Select a dataset, train and enable the model.
+ $input = cli_input(get_string('clienablemodel', 'tool_analytics'));
+ while (!\core_analytics\manager::is_valid($input, '\core_analytics\local\time_splitting\base') && $input !== 'none') {
+ mtrace(get_string('errorunexistingtimesplitting', 'analytics'));
+ $input = cli_input(get_string('clienablemodel', 'tool_analytics'));
+ }
+
+ if ($input === 'none') {
+ exit(0);
+ }
+
+ // Refresh the instance to prevent unexpected issues.
+ $model = new \core_analytics\model($modelobj);
+
+ // Set the time splitting method file and enable it.
+ $model->enable($input);
+
+ mtrace(get_string('trainandpredictmodel', 'tool_analytics'));
+
+ // Train the model with the selected time splitting method and start predicting.
+ $model->train();
+ $model->predict();
+}
+
+exit(0);
--- /dev/null
+<?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/>.
+
+/**
+ * Guesses course start and end dates based on activity logs.
+ *
+ * @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
+ */
+
+define('CLI_SCRIPT', true);
+
+require_once(__DIR__ . '/../../../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+require_once($CFG->dirroot . '/course/lib.php');
+require_once($CFG->dirroot . '/course/format/weeks/lib.php');
+
+$help = "Guesses course start and end dates based on activity logs.
+
+Options:
+--guessstart Guess the course start date (default to true)
+--guessend Guess the course end date (default to true)
+--guessall Guess all start and end dates, even if they are already set (default to false)
+--update Update the db or just notify the guess (default to false)
+--filter Analyser dependant. e.g. A courseid would evaluate the model using a single course (Optional)
+-h, --help Print out this help
+
+Example:
+\$ php admin/tool/analytics/cli/guess_course_start_and_end_dates.php --update=1 --filter=123,321
+";
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(
+ array(
+ 'help' => false,
+ 'guessstart' => true,
+ 'guessend' => true,
+ 'guessall' => false,
+ 'update' => false,
+ 'filter' => false
+ ),
+ array(
+ 'h' => 'help',
+ )
+);
+
+if ($options['help']) {
+ echo $help;
+ exit(0);
+}
+
+if ($options['guessstart'] === false && $options['guessend'] === false && $options['guessall'] === false) {
+ echo $help;
+ exit(0);
+}
+
+// Reformat them as an array.
+if ($options['filter'] !== false) {
+ $options['filter'] = explode(',', clean_param($options['filter'], PARAM_SEQUENCE));
+}
+
+// We need admin permissions.
+\core\session\manager::set_user(get_admin());
+
+$conditions = array('id != 1');
+if (!$options['guessall']) {
+ if ($options['guessstart']) {
+ $conditions[] = '(startdate is null or startdate = 0)';
+ }
+ if ($options['guessend']) {
+ $conditions[] = '(enddate is null or enddate = 0)';
+ }
+}
+
+$coursessql = '';
+$params = null;
+if ($options['filter']) {
+ list($coursessql, $params) = $DB->get_in_or_equal($options['filter'], SQL_PARAMS_NAMED);
+ $conditions[] = 'id ' . $coursessql;
+}
+
+$courses = $DB->get_recordset_select('course', implode(' AND ', $conditions), $params, 'sortorder ASC');
+foreach ($courses as $course) {
+ tool_analytics_calculate_course_dates($course, $options);
+}
+$courses->close();
+
+
+/**
+ * tool_analytics_calculate_course_dates
+ *
+ * @param stdClass $course
+ * @param array $options CLI options
+ * @return void
+ */
+function tool_analytics_calculate_course_dates($course, $options) {
+ global $DB, $OUTPUT;
+
+ $courseman = new \core_analytics\course($course);
+
+ $notification = $course->shortname . ' (id = ' . $course->id . '): ';
+
+ if ($options['guessstart'] || $options['guessall']) {
+
+ $originalstartdate = $course->startdate;
+
+ $guessedstartdate = $courseman->guess_start();
+ if ($guessedstartdate == $originalstartdate) {
+ if (!$guessedstartdate) {
+ $notification .= PHP_EOL . ' ' . get_string('cantguessstartdate', 'tool_analytics');
+ } else {
+ // No need to update.
+ $notification .= PHP_EOL . ' ' . get_string('samestartdate', 'tool_analytics') . ': ' . userdate($guessedstartdate);
+ }
+ } else if (!$guessedstartdate) {
+ $notification .= PHP_EOL . ' ' . get_string('cantguessstartdate', 'tool_analytics');
+ } else {
+ // Update it to something we guess.
+
+ // We set it to $course even if we don't update because may be needed to guess the end one.
+ $course->startdate = $guessedstartdate;
+ $notification .= PHP_EOL . ' ' . get_string('startdate') . ': ' . userdate($guessedstartdate);
+
+ // Two different course updates because week's end date may be recalculated after setting the start date.
+ if ($options['update']) {
+ update_course($course);
+
+ // Refresh course data as end date may have been updated.
+ $course = $DB->get_record('course', array('id' => $course->id));
+ $courseman = new \core_analytics\course($course);
+ }
+ }
+ }
+
+ if ($options['guessend'] || $options['guessall']) {
+
+ $originalenddate = $course->enddate;
+
+ $format = course_get_format($course);
+ $formatoptions = $format->get_format_options();
+
+ if ($course->format === 'weeks' && $formatoptions['automaticenddate']) {
+ // Special treatment for weeks with automatic end date.
+
+ if ($options['update']) {
+ format_weeks::update_end_date($course->id);
+ $course->enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
+ $notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_analytics') . ': ' .
+ userdate($course->enddate);
+ } else {
+ // We can't provide more info without actually updating it in db.
+ $notification .= PHP_EOL . ' ' . get_string('weeksenddatedefault', 'tool_analytics');
+ }
+ } else {
+ $guessedenddate = $courseman->guess_end();
+
+ if ($guessedenddate == $originalenddate) {
+ if (!$guessedenddate) {
+ $notification .= PHP_EOL . ' ' . get_string('cantguessenddate', 'tool_analytics');
+ } else {
+ // No need to update.
+ $notification .= PHP_EOL . ' ' . get_string('sameenddate', 'tool_analytics') . ': ' . userdate($guessedenddate);
+ }
+ } else if (!$guessedenddate) {
+ $notification .= PHP_EOL . ' ' . get_string('cantguessenddate', 'tool_analytics');
+ } else {
+ // Update it to something we guess.
+
+ $course->enddate = $guessedenddate;
+
+ if ($course->enddate > $course->startdate) {
+ $notification .= PHP_EOL . ' ' . get_string('enddate') . ': ' . userdate($course->enddate);
+ } else {
+ $notification .= PHP_EOL . ' ' . get_string('errorendbeforestart', 'analytics', userdate($course->enddate));
+ }
+
+ if ($options['update']) {
+ if ($course->enddate > $course->startdate) {
+ update_course($course);
+ }
+ }
+ }
+ }
+
+ }
+
+ mtrace($notification);
+}
+
+mtrace(get_string('success'));
+
+exit(0);
--- /dev/null
+<?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 file defines tasks performed by the tool.
+ *
+ * @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
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// List of tasks.
+$tasks = array(
+ array(
+ 'classname' => 'tool_analytics\task\train_models',
+ 'blocking' => 0,
+ 'minute' => '0',
+ 'hour' => 'R',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ ),
+ array(
+ 'classname' => 'tool_analytics\task\predict_models',
+ 'blocking' => 0,
+ 'minute' => '0',
+ 'hour' => 'R',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ ),
+);
--- /dev/null
+<?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 tool frontend.
+ *
+ * @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
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+admin_externalpage_setup('analyticmodels', '', null, '', array('pagelayout' => 'report'));
+
+$models = \core_analytics\manager::get_all_models();
+
+echo $OUTPUT->header();
+
+$templatable = new \tool_analytics\output\models_list($models);
+echo $PAGE->get_renderer('tool_analytics')->render($templatable);
+
+echo $OUTPUT->footer();
--- /dev/null
+<?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/>.
+
+/**
+ * Strings for tool_analytics.
+ *
+ * @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
+ */
+
+$string['accuracy'] = 'Accuracy';
+$string['allpredictions'] = 'All predictions';
+$string['analysingsitedata'] = 'Analysing the site';
+$string['analyticmodels'] = 'Analytic models';
+$string['bettercli'] = 'Evaluating models and generating predictions may involve heavy processing. It is advised that you run these actions via the command line interface';
+$string['cantguessstartdate'] = 'Can\'t guess the start date';
+$string['cantguessenddate'] = 'Can\'t guess the end date';
+$string['clienablemodel'] = 'You can enable the model by selecting a time splitting method by its id. Note that you can also enable it later using the web interface (\'none\' to exit)';
+$string['editmodel'] = 'Edit "{$a}" model';
+$string['edittrainedwarning'] = 'This model has already been trained, note that changing its indicators or its time splitting method will delete its previous predictions and start generating the new ones';
+$string['enabled'] = 'Enabled';
+$string['errorcantenablenotimesplitting'] = 'You need to select a time splitting method before enabling the model';
+$string['errornoenabledandtrainedmodels'] = 'There are not enabled and trained models to predict';
+$string['errornoenabledmodels'] = 'There are not enabled models to train';
+$string['errornostaticedit'] = 'Models based on assumptions can not be edited';
+$string['errornostaticevaluated'] = 'Models based on assumptions can not be evaluated, they are always 100% correct according to how they were defined';
+$string['errornostaticlog'] = 'Models based on assumptions can not be evaluated, there is no preformance log';
+$string['evaluate'] = 'Evaluate';
+$string['evaluatemodel'] = 'Evaluate model';
+$string['evaluationinbatches'] = 'The site contents are calculated and stored in batches, during evaluation you can stop the process at any moment, the next time you run it it will continue from the point you stopped it.';
+$string['trainandpredictmodel'] = 'Training model and calculating predictions';
+$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) course duration splitting';
+$string['getpredictionsresults'] = 'Results using {$a->name} course duration splitting';
+$string['extrainfo'] = 'Info';
+$string['generalerror'] = 'Evaluation error. Status code {$a}';
+$string['getpredictions'] = 'Get predictions';
+$string['goodmodel'] = 'This is a good model and it can be used to predict, enable it to start getting predictions.';
+$string['indicators'] = 'Indicators';
+$string['info'] = 'Info';
+$string['insights'] = 'Insights';
+$string['loginfo'] = 'Log extra info';
+$string['modelresults'] = '{$a} results';
+$string['modelslist'] = 'Models list';
+$string['modeltimesplitting'] = 'Time splitting';
+$string['nodatatoevaluate'] = 'There is no data to evaluate the model';
+$string['nodatatopredict'] = 'No new elements to get predictions for';
+$string['nodatatotrain'] = 'There is no new data that can be used for training';
+$string['notdefined'] = 'Not yet defined';
+$string['pluginname'] = 'Analytic models';
+$string['predictionresults'] = 'Prediction results';
+$string['predictmodels'] = 'Predict models';
+$string['predictorresultsin'] = 'Predictor logged information in {$a} directory';
+$string['predictionprocessfinished'] = 'Prediction process finished';
+$string['samestartdate'] = 'Current start date is good';
+$string['sameenddate'] = 'Current end date is good';
+$string['target'] = 'Target';
+$string['trainingprocessfinished'] = 'Training process finished';
+$string['trainingresults'] = 'Training results';
+$string['trainmodels'] = 'Train models';
+$string['viewlog'] = 'Log';
+$string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
+$string['weeksenddatedefault'] = 'End date would be automatically calculated from the course start date';
--- /dev/null
+<?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-related actions.
+ *
+ * @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
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+
+$id = required_param('id', PARAM_INT);
+$action = required_param('action', PARAM_ALPHANUMEXT);
+
+$context = context_system::instance();
+
+require_login();
+
+$model = new \core_analytics\model($id);
+\core_analytics\manager::check_can_manage_models();
+
+$params = array('id' => $id, 'action' => $action);
+$url = new \moodle_url('/admin/tool/analytics/model.php', $params);
+
+switch ($action) {
+
+ case 'edit':
+ $title = get_string('editmodel', 'tool_analytics', $model->get_target()->get_name());
+ break;
+ case 'evaluate':
+ $title = get_string('evaluatemodel', 'tool_analytics');
+ break;
+ case 'getpredictions':
+ $title = get_string('getpredictions', 'tool_analytics');
+ break;
+ case 'log':
+ $title = get_string('viewlog', 'tool_analytics');
+ break;
+ case 'enable':
+ $title = get_string('enable');
+ break;
+ case 'disable':
+ $title = get_string('disable');
+ break;
+
+ default:
+ throw new moodle_exception('errorunknownaction', 'analytics');
+}
+
+$PAGE->set_context($context);
+$PAGE->set_url($url);
+$PAGE->set_pagelayout('report');
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+
+switch ($action) {
+
+ case 'enable':
+ $model->enable();
+ redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+
+ case 'disable':
+ $model->update(0, false, false);
+ redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+
+ case 'edit':
+
+ if ($model->is_static()) {
+ echo $OUTPUT->header();
+ throw new moodle_exception('errornostaticedit', 'tool_analytics');
+ }
+
+ $customdata = array(
+ 'id' => $model->get_id(),
+ 'model' => $model,
+ 'indicators' => $model->get_potential_indicators(),
+ 'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods()
+ );
+ $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
+
+ if ($mform->is_cancelled()) {
+ redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+
+ } else if ($data = $mform->get_data()) {
+ confirm_sesskey();
+
+ // Converting option names to class names.
+ $indicators = array();
+ foreach ($data->indicators as $indicator) {
+ $indicatorclass = \tool_analytics\output\helper::option_to_class($indicator);
+ $indicators[] = \core_analytics\manager::get_indicator($indicatorclass);
+ }
+ $timesplitting = \tool_analytics\output\helper::option_to_class($data->timesplitting);
+ $model->update($data->enabled, $indicators, $timesplitting);
+ redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+ }
+
+ echo $OUTPUT->header();
+
+ $modelobj = $model->get_model_obj();
+
+ $callable = array('\tool_analytics\output\helper', 'class_to_option');
+ $modelobj->indicators = array_map($callable, json_decode($modelobj->indicators));
+ $modelobj->timesplitting = \tool_analytics\output\helper::class_to_option($modelobj->timesplitting);
+ $mform->set_data($modelobj);
+ $mform->display();
+ break;
+
+ case 'evaluate':
+ echo $OUTPUT->header();
+
+ if ($model->is_static()) {
+ throw new moodle_exception('errornostaticevaluate', 'tool_analytics');
+ }
+
+ // Web interface is used by people who can not use CLI nor code stuff, always use
+ // cached stuff as they will change the model through the web interface as well
+ // which invalidates the previously analysed stuff.
+ $results = $model->evaluate(array('reuseprevanalysed' => true));
+ $renderer = $PAGE->get_renderer('tool_analytics');
+ echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
+ break;
+
+ case 'getpredictions':
+ echo $OUTPUT->header();
+
+ $trainresults = $model->train();
+ $trainlogs = $model->get_analyser()->get_logs();
+
+ // Looks dumb to get a new instance but better be conservative.
+ $model = new \core_analytics\model($model->get_model_obj());
+ $predictresults = $model->predict();
+ $predictlogs = $model->get_analyser()->get_logs();
+
+ $renderer = $PAGE->get_renderer('tool_analytics');
+ echo $renderer->render_get_predictions_results($trainresults, $trainlogs, $predictresults, $predictlogs);
+ break;
+
+ case 'log':
+ echo $OUTPUT->header();
+
+ if ($model->is_static()) {
+ throw new moodle_exception('errornostaticlog', 'tool_analytics');
+ }
+
+ $renderer = $PAGE->get_renderer('tool_analytics');
+ $modellogstable = new \tool_analytics\output\model_logs('model-' . $model->get_id(), $model);
+ echo $renderer->render_table($modellogstable);
+ break;
+}
+
+echo $OUTPUT->footer();
--- /dev/null
+<?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/>.
+
+/**
+ * Adds settings links to admin tree.
+ *
+ * @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
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$ADMIN->add('reports', new admin_externalpage('analyticmodels', get_string('analyticmodels', 'tool_analytics'),
+ "$CFG->wwwroot/$CFG->admin/tool/analytics/index.php", 'moodle/analytics:managemodels'));
--- /dev/null
+{{!
+ 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/>.
+}}
+{{!
+ @template tool_analytics/models_list
+
+ Template for models list.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * none
+
+ Example context (json):
+ {
+ "models": [
+ {
+ "target": "Prevent devs at risk",
+ "enabled": 1,
+ "indicators": [
+ "Indicator 1",
+ "Indicator 2",
+ "Indicator 3",
+ "Indicator 4"
+ ],
+ "timesplitting": "Quarters",
+ "noinsights": "No insights available yet"
+ }
+ ],
+ "warnings": {
+ "message": "Hey, this is a warning"
+ }
+ }
+}}
+
+{{#warnings}}
+ {{> core/notification_warning}}
+{{/warnings}}
+<div class="box">
+ <table class="generaltable fullwidth">
+ <caption>{{#str}}modelslist, tool_analytics{{/str}}</caption>
+ <thead>
+ <tr>
+ <th scope="col">{{#str}}target, tool_analytics{{/str}}</th>
+ <th scope="col">{{#str}}enabled, tool_analytics{{/str}}</th>
+ <th scope="col">{{#str}}indicators, tool_analytics{{/str}}</th>
+ <th scope="col">{{#str}}modeltimesplitting, tool_analytics{{/str}}</th>
+ <th scope="col">{{#str}}insights, tool_analytics{{/str}}</th>
+ <th scope="col">{{#str}}actions{{/str}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#models}}
+ <tr>
+ <td>{{target}}</td>
+ <td>
+ {{#enabled}}
+ {{#pix}}i/checked, core, {{#str}}yes{{/str}}{{/pix}}
+ {{/enabled}}
+ {{^enabled}}
+ {{#str}}no{{/str}}
+ {{/enabled}}
+ </td>
+ <td>
+ <ul>
+ {{#indicators}}
+ <li>{{.}}</li>
+ {{/indicators}}
+ </ul>
+ </td>
+ <td>
+ {{#timesplitting}}{{timesplitting}}{{/timesplitting}}{{^timesplitting}}{{#str}}notdefined, tool_analytics{{/str}}{{/timesplitting}}
+ </td>
+ <td>
+ {{! models_list renderer is responsible of sending one or the other}}
+ {{#insights}}
+ {{> core/single_select }}
+ {{/insights}}
+ {{#noinsights}}
+ {{.}}
+ {{/noinsights}}
+ </td>
+ <td>
+ {{#actions}}
+ {{> core/action_menu}}
+ {{/actions}}
+ </td>
+ </tr>
+ {{/models}}
+ </tbody>
+ </table>
+</div>
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Version details
+ * Version details.
*
- * @package auth_fc
- * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @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
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2017050500; // Requires this Moodle version
-$plugin->component = 'auth_fc'; // Full name of the plugin (used for diagnostics)
+$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2017050500; // Requires this Moodle version.
+$plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
-global $CFG;
-require_once($CFG->libdir . '/behat/classes/behat_selectors.php');
-
/**
* Renderer for behat tool web features
*
* @return string HTML code
*/
public function render_stepsdefinitions($stepsdefinitions, $form) {
+ global $CFG;
+ require_once($CFG->libdir . '/behat/classes/behat_selectors.php');
$html = $this->generic_info();
And I should see "Test workshop name"
And I follow "Test assignment name"
And I should see "Test assignment description"
- And I follow "C1"
+ And I am on "Course 1" course homepage
And I follow "Test assignment name with scale"
And I follow "Edit settings"
And the field "Type" matches value "Scale"
| fullname | course | gradecategory |
| Grade sub category 2 | C1 | Grade category 1 |
When I log in as "admin"
- And I am on course index
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
Then I should see "Grade category 1"
$this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
}
- /**
- * Select item from autocomplete list.
- *
- * @Given /^I click on "([^"]*)" item in the autocomplete list$/
- *
- * @param string $item
- */
- public function i_click_on_item_in_the_autocomplete_list($item) {
- $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//li//span//span[contains(.,'" . $item . "')]";
-
- $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
- }
}
And I follow "Home"
And I navigate to "Competencies > Learning plan templates" in site administration
And I click on ".template-userplans" "css_element" in the "Science template" "table_row"
- And I click on ".form-autocomplete-downarrow" "css_element"
+ And I open the autocomplete suggestions list
And I click on "Admin" item in the autocomplete list
And I press key "27" in the field "Select users to create learning plans for"
When I click on "Create learning plans" "button"
And I log in as "admin"
And I navigate to "Event monitoring rules" node in "Site administration > Reports"
And I click on "Enable" "link"
- And I am on site homepage
And I am on "Course 1" course homepage
And I navigate to "Event monitoring rules" node in "Course administration > Reports"
And I press "Add a new rule"
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+defined('MOODLE_INTERNAL') || die();
+
/**
* This class will check all the default values existing in the DB
* match those specified in the xml specs
/**
* Init method, every subclass will have its own
*/
- function init() {
+ public function init() {
$this->introstr = 'confirmcheckdefaults';
parent::init();
- // Set own core attributes
+ // Set own core attributes.
- // Set own custom attributes
+ // Set own custom attributes.
- // Get needed strings
+ // Get needed strings.
$this->loadStrings(array(
'wrongdefaults' => 'tool_xmldb',
'nowrongdefaultsfound' => 'tool_xmldb',
));
}
- protected function check_table(xmldb_table $xmldb_table, array $metacolumns) {
+ protected function check_table(xmldb_table $xmldbtable, array $metacolumns) {
$o = '';
- $wrong_fields = array();
+ $wrongfields = array();
+
+ // Get and process XMLDB fields.
+ if ($xmldbfields = $xmldbtable->getFields()) {
+ $o .= ' <ul>';
+ foreach ($xmldbfields as $xmldbfield) {
- // Get and process XMLDB fields
- if ($xmldb_fields = $xmldb_table->getFields()) {
- $o.=' <ul>';
- foreach ($xmldb_fields as $xmldb_field) {
+ // Get the default value for the field.
+ $xmldbdefault = $xmldbfield->getDefault();
- // Get the default value for the field
- $xmldbdefault = $xmldb_field->getDefault();
+ // Char fields with not null currently have default '' when actually installed.
+ if ($xmldbdefault === null && $xmldbfield->getType() === XMLDB_TYPE_CHAR &&
+ $xmldbfield->getNotNull()) {
+ $xmldbdefault = '';
+ }
+ if ($xmldbdefault !== null) {
+ $xmldbdefault = (string)$xmldbdefault;
+ }
- // If the metadata for that column doesn't exist or 'id' field found, skip
- if (!isset($metacolumns[$xmldb_field->getName()]) or $xmldb_field->getName() == 'id') {
+ // If the metadata for that column doesn't exist or 'id' field found, skip.
+ if (!isset($metacolumns[$xmldbfield->getName()]) or $xmldbfield->getName() == 'id') {
continue;
}
- // To variable for better handling
- $metacolumn = $metacolumns[$xmldb_field->getName()];
+ // To variable for better handling.
+ $metacolumn = $metacolumns[$xmldbfield->getName()];
- // Going to check this field in DB
- $o.=' <li>' . $this->str['field'] . ': ' . $xmldb_field->getName() . ' ';
+ // Going to check this field in DB.
+ $o .= ' <li>' . $this->str['field'] . ': ' . $xmldbfield->getName() . ' ';
- // get the value of the physical default (or blank if there isn't one)
- if ($metacolumn->has_default==1) {
+ // Get the value of the physical default (or blank if there isn't one).
+ if ($metacolumn->has_default == 1) {
$physicaldefault = $metacolumn->default_value;
- }
- else {
- $physicaldefault = '';
+ } else {
+ $physicaldefault = null;
}
- // there *is* a default and it's wrong
- if ($physicaldefault != $xmldbdefault) {
- $info = '('.$this->str['expected']." '$xmldbdefault', ".$this->str['actual'].
- " '$physicaldefault')";
- $o.='<font color="red">' . $this->str['wrong'] . " $info</font>";
- // Add the wrong field to the list
+ // There *is* a default and it's wrong.
+ if ($physicaldefault !== $xmldbdefault) {
+ $xmldbtext = self::display_default($xmldbdefault);
+ $physicaltext = self::display_default($physicaldefault);
+ $info = "({$this->str['expected']} {$xmldbtext}, {$this->str['actual']} {$physicaltext})";
+ $o .= '<font color="red">' . $this->str['wrong'] . " $info</font>";
+ // Add the wrong field to the list.
$obj = new stdClass();
- $obj->table = $xmldb_table;
- $obj->field = $xmldb_field;
+ $obj->table = $xmldbtable;
+ $obj->field = $xmldbfield;
$obj->physicaldefault = $physicaldefault;
$obj->xmldbdefault = $xmldbdefault;
- $wrong_fields[] = $obj;
+ $wrongfields[] = $obj;
} else {
- $o.='<font color="green">' . $this->str['ok'] . '</font>';
+ $o .= '<font color="green">' . $this->str['ok'] . '</font>';
}
- $o.='</li>';
+ $o .= '</li>';
}
- $o.=' </ul>';
+ $o .= ' </ul>';
}
- return array($o, $wrong_fields);
+ return array($o, $wrongfields);
+ }
+
+ /**
+ * Converts a default value suitable for display.
+ *
+ * @param string|null $defaultvalue Default value
+ * @return string Displayed version
+ */
+ protected static function display_default($defaultvalue) {
+ if ($defaultvalue === null) {
+ return '-';
+ } else {
+ return "'" . s($defaultvalue) . "'";
+ }
}
- protected function display_results(array $wrong_fields) {
+ protected function display_results(array $wrongfields) {
global $DB;
$dbman = $DB->get_manager();
$s = '';
$r = '<table class="generaltable boxaligncenter boxwidthwide" border="0" cellpadding="5" cellspacing="0" id="results">';
- $r.= ' <tr><td class="generalboxcontent">';
- $r.= ' <h2 class="main">' . $this->str['searchresults'] . '</h2>';
- $r.= ' <p class="centerpara">' . $this->str['wrongdefaults'] . ': ' . count($wrong_fields) . '</p>';
- $r.= ' </td></tr>';
- $r.= ' <tr><td class="generalboxcontent">';
-
- // If we have found wrong defaults inform about them
- if (count($wrong_fields)) {
- $r.= ' <p class="centerpara">' . $this->str['yeswrongdefaultsfound'] . '</p>';
- $r.= ' <ul>';
- foreach ($wrong_fields as $obj) {
- $xmldb_table = $obj->table;
- $xmldb_field = $obj->field;
- $physicaldefault = $obj->physicaldefault;
- $xmldbdefault = $obj->xmldbdefault;
-
- // get the alter table command
- $sqlarr = $dbman->generator->getAlterFieldSQL($xmldb_table, $xmldb_field);
-
- $r.= ' <li>' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' .
- $this->str['field'] . ': ' . $xmldb_field->getName() . ', ' .
- $this->str['expected'] . ' ' . "'$xmldbdefault'" . ' ' .
- $this->str['actual'] . ' ' . "'$physicaldefault'" . '</li>';
- // Add to output if we have sentences
+ $r .= ' <tr><td class="generalboxcontent">';
+ $r .= ' <h2 class="main">' . $this->str['searchresults'] . '</h2>';
+ $r .= ' <p class="centerpara">' . $this->str['wrongdefaults'] . ': ' . count($wrongfields) . '</p>';
+ $r .= ' </td></tr>';
+ $r .= ' <tr><td class="generalboxcontent">';
+
+ // If we have found wrong defaults inform about them.
+ if (count($wrongfields)) {
+ $r .= ' <p class="centerpara">' . $this->str['yeswrongdefaultsfound'] . '</p>';
+ $r .= ' <ul>';
+ foreach ($wrongfields as $obj) {
+ $xmldbtable = $obj->table;
+ $xmldbfield = $obj->field;
+ $physicaltext = self::display_default($obj->physicaldefault);
+ $xmldbtext = self::display_default($obj->xmldbdefault);
+
+ // Get the alter table command.
+ $sqlarr = $dbman->generator->getAlterFieldSQL($xmldbtable, $xmldbfield);
+
+ $r .= ' <li>' . $this->str['table'] . ': ' . $xmldbtable->getName() . '. ' .
+ $this->str['field'] . ': ' . $xmldbfield->getName() . ', ' .
+ $this->str['expected'] . ' ' . $xmldbtext . ' ' .
+ $this->str['actual'] . ' ' . $physicaltext . '</li>';
+ // Add to output if we have sentences.
if ($sqlarr) {
$sqlarr = $dbman->generator->getEndedStatements($sqlarr);
- $s.= '<code>' . str_replace("\n", '<br />', implode('<br />', $sqlarr)) . '</code><br />';
+ $s .= '<code>' . str_replace("\n", '<br />', implode('<br />', $sqlarr)) . '</code><br />';
}
}
- $r.= ' </ul>';
- // Add the SQL statements (all together)
- $r.= '<hr />' . $s;
+ $r .= ' </ul>';
+ // Add the SQL statements (all together).
+ $r .= '<hr />' . $s;
} else {
- $r.= ' <p class="centerpara">' . $this->str['nowrongdefaultsfound'] . '</p>';
+ $r .= ' <p class="centerpara">' . $this->str['nowrongdefaultsfound'] . '</p>';
}
- $r.= ' </td></tr>';
- $r.= ' <tr><td class="generalboxcontent">';
- // Add the complete log message
- $r.= ' <p class="centerpara">' . $this->str['completelogbelow'] . '</p>';
- $r.= ' </td></tr>';
- $r.= '</table>';
+ $r .= ' </td></tr>';
+ $r .= ' <tr><td class="generalboxcontent">';
+ // Add the complete log message.
+ $r .= ' <p class="centerpara">' . $this->str['completelogbelow'] . '</p>';
+ $r .= ' </td></tr>';
+ $r .= '</table>';
return $r;
}
break;
case 'delete':
- $token = $webservicemanager->get_created_by_user_ws_token($USER->id, $tokenid);
+ $token = $webservicemanager->get_token_by_id_with_details($tokenid);
+
+ if ($token->creatorid != $USER->id) {
+ require_capability("moodle/webservice:managealltokens", context_system::instance());
+ }
//Delete the token
if ($confirm and confirm_sesskey()) {
--- /dev/null
+<?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/>.
+
+/**
+ * Extension to show an error message if the selected predictor is not available.
+ *
+ * @package core_analytics
+ * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_analytics;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../../lib/adminlib.php');
+
+/**
+ * Extension to show an error message if the selected predictor is not available.
+ *
+ * @package core_analytics
+ * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_setting_predictor extends \admin_setting_configselect {
+
+ /**
+ * Save a setting
+ *
+ * @param string $data
+ * @return string empty of error string
+ */
+ public function write_setting($data) {
+ if (!$this->load_choices() or empty($this->choices)) {
+ return '';
+ }
+ if (!array_key_exists($data, $this->choices)) {
+ return '';
+ }
+
+ // Calling it here without checking if it is ready because we check it below and show it as a controlled case.
+ $selectedprocessor = \core_analytics\manager::get_predictions_processor($data, false);
+ $isready = $selectedprocessor->is_ready();
+ if ($isready !== true) {
+ return get_string('errorprocessornotready', 'analytics', $isready);
+ }
+
+ return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Any element analysers can analyse.
+ *
+ * @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;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Any element analysers can analyse.
+ *
+ * @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
+ */
+interface analysable {
+
+ /**
+ * Max timestamp.
+ */
+ const MAX_TIME = 9999999999;
+
+ /**
+ * The analysable unique identifier in the site.
+ *
+ * @return int.
+ */
+ public function get_id();
+
+ /**
+ * The analysable context.
+ *
+ * @return \context
+ */
+ public function get_context();
+
+ /**
+ * The start of the analysable if there is one.
+ *
+ * @return int|false
+ */
+ public function get_start();
+
+ /**
+ * The end of the analysable if there is one.
+ *
+ * @return int|false
+ */
+ public function get_end();
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Calculable dataset items abstract 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;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Calculable dataset items abstract 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 calculable {
+
+ /**
+ * Neutral calculation outcome.
+ */
+ const OUTCOME_NEUTRAL = 0;
+
+ /**
+ * Very positive calculation outcome.
+ */
+ const OUTCOME_VERY_POSITIVE = 1;
+
+ /**
+ * Positive calculation outcome.
+ */
+ const OUTCOME_OK = 2;
+
+ /**
+ * Negative calculation outcome.
+ */
+ const OUTCOME_NEGATIVE = 3;
+
+ /**
+ * Very negative calculation outcome.
+ */
+ const OUTCOME_VERY_NEGATIVE = 4;
+
+ /**
+ * @var array[]
+ */
+ protected $sampledata = array();
+
+ /**
+ * Returns a visible name for the indicator.
+ *
+ * Used as column identificator.
+ *
+ * Defaults to the indicator class name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return '\\' . get_called_class();
+ }
+
+ /**
+ * The class id is the calculable class full qualified class name.
+ *
+ * @return string
+ */
+ public function get_id() {
+ return '\\' . get_class($this);
+ }
+
+ /**
+ * add_sample_data
+ *
+ * @param array $data
+ * @return void
+ */
+ public function add_sample_data($data) {
+ $this->sampledata = $this->array_merge_recursive_keep_keys($this->sampledata, $data);
+ }
+
+ /**
+ * clear_sample_data
+ *
+ * @return void
+ */
+ public function clear_sample_data() {
+ $this->sampledata = array();
+ }
+
+ /**
+ * Returns the visible value of the calculated value.
+ *
+ * @param float $value
+ * @param string|false $subtype
+ * @return string
+ */
+ public function get_display_value($value, $subtype = false) {
+ return $value;
+ }
+
+ /**
+ * Returns how good the calculated value is.
+ *
+ * Use one of \core_analytics\calculable::OUTCOME_* values.
+ *
+ * @param float $value
+ * @param string|false $subtype
+ * @return int
+ */
+ abstract public function get_calculation_outcome($value, $subtype = false);
+
+ /**
+ * Retrieve the specified element associated to $sampleid.
+ *
+ * @param string $elementname
+ * @param int $sampleid
+ * @return \stdClass|false An \stdClass object or false if it can not be found.
+ */
+ protected function retrieve($elementname, $sampleid) {
+ if (empty($this->sampledata[$sampleid]) || empty($this->sampledata[$sampleid][$elementname])) {
+ // We don't throw an exception because indicators should be able to
+ // try multiple tables until they find something they can use.
+ return false;
+ }
+ return $this->sampledata[$sampleid][$elementname];
+ }
+
+ /**
+ * Returns the number of weeks a time range contains.
+ *
+ * Useful for calculations that depend on the time range duration. Note that it returns
+ * a float, rounding the float may lead to inaccurate results.
+ *
+ * @param int $starttime
+ * @param int $endtime
+ * @return float
+ */
+ protected function get_time_range_weeks_number($starttime, $endtime) {
+ if ($endtime <= $starttime) {
+ throw new \coding_exception('End time timestamp should be greater than start time.');
+ }
+
+ $starttimedt = new \DateTime();
+ $starttimedt->setTimestamp($starttime);
+ $starttimedt->setTimezone(new \DateTimeZone('UTC'));
+ $endtimedt = new \DateTime();
+ $endtimedt->setTimestamp($endtime);
+ $endtimedt->setTimezone(new \DateTimeZone('UTC'));
+
+ $diff = $endtimedt->getTimestamp() - $starttimedt->getTimestamp();
+ return $diff / WEEKSECS;
+ }
+
+ /**
+ * Limits the calculated value to the minimum and maximum values.
+ *
+ * @param float $calculatedvalue
+ * @return float|null
+ */
+ protected function limit_value($calculatedvalue) {
+ return max(min($calculatedvalue, static::get_max_value()), static::get_min_value());
+ }
+
+ /**
+ * Classifies the provided value into the provided range according to the ranges predicates.
+ *
+ * Use:
+ * - eq as 'equal'
+ * - ne as 'not equal'
+ * - lt as 'lower than'
+ * - le as 'lower or equal than'
+ * - gt as 'greater than'
+ * - ge as 'greater or equal than'
+ *
+ * @throws \coding_exception
+ * @param int|float $value
+ * @param array $ranges e.g. [ ['lt', 20], ['ge', 20] ]
+ * @return float
+ */
+ protected function classify_value($value, $ranges) {
+
+ // To automatically return calculated values from min to max values.
+ $rangeweight = (static::get_max_value() - static::get_min_value()) / (count($ranges) - 1);
+
+ foreach ($ranges as $key => $range) {
+
+ $match = false;
+
+ if (count($range) != 2) {
+ throw new \coding_exception('classify_value() $ranges array param should contain 2 items, the predicate ' .
+ 'e.g. greater (gt), lower or equal (le)... and the value.');
+ }
+
+ list($predicate, $rangevalue) = $range;
+
+ switch ($predicate) {
+ case 'eq':
+ if ($value == $rangevalue) {
+ $match = true;
+ }
+ break;
+ case 'ne':
+ if ($value != $rangevalue) {
+ $match = true;
+ }
+ break;
+ case 'lt':
+ if ($value < $rangevalue) {
+ $match = true;
+ }
+ break;
+ case 'le':
+ if ($value <= $rangevalue) {
+ $match = true;
+ }
+ break;
+ case 'gt':
+ if ($value > $rangevalue) {
+ $match = true;
+ }
+ break;
+ case 'ge':
+ if ($value >= $rangevalue) {
+ $match = true;
+ }
+ break;
+ default:
+ throw new \coding_exception('Unrecognised predicate ' . $predicate . '. Please use eq, ne, lt, le, ge or gt.');
+ }
+
+ // Calculate and return a linear calculated value for the provided value.
+ if ($match) {
+ return round(static::get_min_value() + ($rangeweight * $key), 2);
+ }
+ }
+
+ throw new \coding_exception('The provided value "' . $value . '" can not be fit into any of the provided ranges, you ' .
+ 'should provide ranges for all possible values.');
+ }
+
+ /**
+ * Merges arrays recursively keeping the same keys the original arrays have.
+ *
+ * @link http://php.net/manual/es/function.array-merge-recursive.php#114818
+ * @return array
+ */
+ private function array_merge_recursive_keep_keys() {
+ $arrays = func_get_args();
+ $base = array_shift($arrays);
+
+ foreach ($arrays as $array) {
+ reset($base);
+ while (list($key, $value) = each($array)) {
+ if (is_array($value) && !empty($base[$key]) && is_array($base[$key])) {
+ $base[$key] = $this->array_merge_recursive_keep_keys($base[$key], $value);
+ } else {
+ if (isset($base[$key]) && is_int($key)) {
+ $key++;
+ }
+ $base[$key] = $value;
+ }
+ }
+ }
+
+ return $base;
+ }
+}
--- /dev/null
+<?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/>.
+
+/**
+ * Moodle course analysable
+ *
+ * @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;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/course/lib.php');
+require_once($CFG->dirroot . '/lib/gradelib.php');
+require_once($CFG->dirroot . '/lib/enrollib.php');
+
+/**
+ * Moodle course analysable
+ *
+ * @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
+ */
+class course implements \core_analytics\analysable {
+
+ /**
+ * @var \core_analytics\course[] $instances
+ */
+ protected static $instances = array();
+
+ /**
+ * Course object
+ *
+ * @var \stdClass
+ */
+ protected $course = null;
+
+ /**
+ * The course context.
+ *
+ * @var \context_course
+ */
+ protected $coursecontext = null;
+
+ /**
+ * The course activities organized by activity type.
+ *
+ * @var array
+ */
+ protected $courseactivities = array();
+
+ /**
+ * Course start time.
+ *
+ * @var int
+ */
+ protected $starttime = null;
+
+
+ /**
+ * Has the course already started?
+ *
+ * @var bool
+ */
+ protected $started = null;
+
+ /**
+ * Course end time.
+ *
+ * @var int
+ */
+ protected $endtime = null;
+
+ /**
+ * Is the course finished?
+ *
+ * @var bool
+ */
+ protected $finished = null;
+
+ /**
+ * Course students ids.
+ *
+ * @var int[]
+ */
+ protected $studentids = [];
+
+
+ /**
+ * Course teachers ids
+ *
+ * @var int[]
+ */
+ protected $teacherids = [];
+
+ /**
+ * Cached copy of the total number of logs in the course.
+ *
+ * @var int
+ */
+ protected $ntotallogs = null;
+
+ /**
+ * Course manager constructor.
+ *
+ * Use self::instance() instead to get cached copies of the course. Instances obtained
+ * through this constructor will not be cached.
+ *
+ * Loads course students and teachers.
+ *
+ * @param int|stdClass $course Course id
+ * @return void
+ */
+ public function __construct($course) {
+
+ if (is_scalar($course)) {
+ $this->course = get_course($course);
+ } else {
+ $this->course = $course;
+ }
+
+ $this->coursecontext = \context_course::instance($this->course->id);
+
+ $this->now = time();
+
+ // Get the course users, including users assigned to student and teacher roles at an higher context.
+ $studentroles = array_keys(get_archetype_roles('student'));
+ $this->studentids = $this->get_user_ids($studentroles);
+
+ $teacherroles = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher'));
+ $this->teacherids = $this->get_user_ids($teacherroles);
+ }
+
+ /**
+ * Returns an analytics course instance.
+ *
+ * @param int|stdClass $course Course id
+ * @return \core_analytics\course
+ */
+ public static function instance($course) {
+
+ $courseid = $course;
+ if (!is_scalar($courseid)) {
+ $courseid = $course->id;
+ }
+
+ if (!empty(self::$instances[$courseid])) {
+ return self::$instances[$courseid];
+ }
+
+ $instance = new \core_analytics\course($course);
+ self::$instances[$courseid] = $instance;
+ return self::$instances[$courseid];
+ }
+
+ /**
+ * Clears all statically cached instances.
+ *
+ * @return void
+ */
+ public static function reset_caches() {
+ self::$instances = array();
+ }
+
+ /**
+ * get_id
+ *
+ * @return int
+ */
+ public function get_id() {
+ return $this->course->id;
+ }
+
+ /**
+ * get_context
+ *
+ * @return \context
+ */
+ public function get_context() {
+ if ($this->coursecontext === null) {
+ $this->coursecontext = \context_course::instance($this->course->id);
+ }
+ return $this->coursecontext;
+ }
+
+ /**
+ * Get the course start timestamp.
+ *
+ * @return int Timestamp or 0 if has not started yet.
+ */
+ public function get_start() {
+
+ if ($this->starttime !== null) {
+ return $this->starttime;
+ }
+
+ // The field always exist but may have no valid if the course is created through a sync process.
+ if (!empty($this->course->startdate)) {
+ $this->starttime = (int)$this->course->startdate;
+ } else {
+ $this->starttime = 0;
+ }
+
+ return $this->starttime;
+ }
+
+ /**
+ * Guesses the start of the course based on students' activity and enrolment start dates.
+ *
+ * @return int
+ */
+ public function guess_start() {
+ global $DB;
+
+ if (!$this->get_total_logs()) {
+ // Can't guess.
+ return 0;
+ }
+
+ if (!$logstore = \core_analytics\manager::get_analytics_logstore()) {
+ return 0;
+ }
+
+ // We first try to find current course student logs.
+ $firstlogs = array();
+ foreach ($this->studentids as $studentid) {
+ // Grrr, we are limited by logging API, we could do this easily with a
+ // select min(timecreated) from xx where courseid = yy group by userid.
+
+ // Filters based on the premise that more than 90% of people will be using
+ // standard logstore, which contains a userid, contextlevel, contextinstanceid index.
+ $select = "userid = :userid AND contextlevel = :contextlevel AND contextinstanceid = :contextinstanceid";
+ $params = array('userid' => $studentid, 'contextlevel' => CONTEXT_COURSE, 'contextinstanceid' => $this->get_id());
+ $events = $logstore->get_events_select($select, $params, 'timecreated ASC', 0, 1);
+ if ($events) {
+ $event = reset($events);
+ $firstlogs[] = $event->timecreated;
+ }
+ }
+ if (empty($firstlogs)) {
+ // Can't guess if no student accesses.
+ return 0;
+ }
+
+ sort($firstlogs);
+ $firstlogsmedian = $this->median($firstlogs);
+
+ $studentenrolments = enrol_get_course_users($this->get_id(), $this->studentids);
+ if (empty($studentenrolments)) {
+ return 0;
+ }
+
+ $enrolstart = array();
+ foreach ($studentenrolments as $studentenrolment) {
+ $enrolstart[] = ($studentenrolment->uetimestart) ? $studentenrolment->uetimestart : $studentenrolment->uetimecreated;
+ }
+ sort($enrolstart);
+ $enrolstartmedian = $this->median($enrolstart);
+
+ return intval(($enrolstartmedian + $firstlogsmedian) / 2);
+ }
+
+ /**
+ * Get the course end timestamp.
+ *
+ * @return int Timestamp or 0 if time end was not set.
+ */
+ public function get_end() {
+ global $DB;
+
+ if ($this->endtime !== null) {
+ return $this->endtime;
+ }
+
+ // The enddate field is only available from Moodle 3.2 (MDL-22078).
+ if (!empty($this->course->enddate)) {
+ $this->endtime = (int)$this->course->enddate;
+ return $this->endtime;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get the course end timestamp.
+ *
+ * @return int Timestamp, \core_analytics\analysable::MAX_TIME if we don't know but ongoing and 0 if we can not work it out.
+ */
+ public function guess_end() {
+ global $DB;
+
+ if ($this->get_total_logs() === 0) {
+ // No way to guess if there are no logs.
+ $this->endtime = 0;
+ return $this->endtime;
+ }
+
+ list($filterselect, $filterparams) = $this->course_students_query_filter('ula');
+
+ // Consider the course open if there are still student accesses.
+ $monthsago = time() - (WEEKSECS * 4 * 2);
+ $select = $filterselect . ' AND timeaccess > :timeaccess';
+ $params = $filterparams + array('timeaccess' => $monthsago);
+ $sql = "SELECT timeaccess FROM {user_lastaccess} ula
+ JOIN {enrol} e ON e.courseid = ula.courseid
+ JOIN {user_enrolments} ue ON e.id = ue.enrolid AND ue.userid = ula.userid
+ WHERE $select";
+ if ($records = $DB->get_records_sql($sql, $params)) {
+ return 0;
+ }
+
+ $sql = "SELECT timeaccess FROM {user_lastaccess} ula
+ JOIN {enrol} e ON e.courseid = ula.courseid
+ JOIN {user_enrolments} ue ON e.id = ue.enrolid AND ue.userid = ula.userid
+ WHERE $filterselect AND ula.timeaccess != 0
+ ORDER BY timeaccess DESC";
+ $studentlastaccesses = $DB->get_fieldset_sql($sql, $filterparams);
+ if (empty($studentlastaccesses)) {
+ return 0;
+ }
+ sort($studentlastaccesses);
+
+ return $this->median($studentlastaccesses);
+ }
+
+ /**
+ * Returns a course plain object.
+ *
+ * @return \stdClass
+ */
+ public function get_course_data() {
+ return $this->course;
+ }
+
+ /**
+ * Is the course valid to extract indicators from it?
+ *
+ * @return bool
+ */
+ public function is_valid() {
+
+ if (!$this->was_started() || !$this->is_finished()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Has the course started?
+ *
+ * @return bool
+ */
+ public function was_started() {
+
+ if ($this->started === null) {
+ if ($this->get_start() === 0 || $this->now < $this->get_start()) {
+ // Not yet started.
+ $this->started = false;
+ } else {
+ $this->started = true;
+ }
+ }
+
+ return $this->started;
+ }
+
+ /**
+ * Has the course finished?
+ *
+ * @return bool
+ */
+ public function is_finished() {
+
+ if ($this->finished === null) {
+ $endtime = $this->get_end();
+ if ($endtime === 0 || $this->now < $endtime) {
+ // It is not yet finished or no idea when it finishes.
+ $this->finished = false;
+ } else {
+ $this->finished = true;
+ }
+ }
+
+ return $this->finished;
+ }
+
+ /**
+ * Returns a list of user ids matching the specified roles in this course.
+ *
+ * @param array $roleids
+ * @return array
+ */
+ public function get_user_ids($roleids) {
+
+ // We need to index by ra.id as a user may have more than 1 $roles role.
+ $records = get_role_users($roleids, $this->coursecontext, true, 'ra.id, u.id AS userid, r.id AS roleid', 'ra.id ASC');
+
+ // If a user have more than 1 $roles role array_combine will discard the duplicate.
+ $callable = array($this, 'filter_user_id');
+ $userids = array_values(array_map($callable, $records));
+ return array_combine($userids, $userids);
+ }
+
+ /**
+ * Returns the course students.
+ *
+ * @return stdClass[]
+ */
+ public function get_students() {
+ return $this->studentids;
+ }
+
+ /**
+ * Returns the total number of student logs in the course
+ *
+ * @return int
+ */
+ public function get_total_logs() {
+ global $DB;
+
+ // No logs if no students.
+ if (empty($this->studentids)) {
+ return 0;
+ }
+
+ if ($this->ntotallogs === null) {
+ list($filterselect, $filterparams) = $this->course_students_query_filter();
+ if (!$logstore = \core_analytics\manager::get_analytics_logstore()) {
+ $this->ntotallogs = 0;
+ } else {
+ $this->ntotallogs = $logstore->get_events_select_count($filterselect, $filterparams);
+ }
+ }
+
+ return $this->ntotallogs;
+ }
+
+ /**
+ * Returns all the activities of the provided type the course has.
+ *
+ * @param string $activitytype
+ * @return array
+ */
+ public function get_all_activities($activitytype) {
+
+ // Using is set because we set it to false if there are no activities.
+ if (!isset($this->courseactivities[$activitytype])) {
+ $modinfo = get_fast_modinfo($this->get_course_data(), -1);
+ $instances = $modinfo->get_instances_of($activitytype);
+
+ if ($instances) {
+ $this->courseactivities[$activitytype] = array();
+ foreach ($instances as $instance) {
+ // By context.
+ $this->courseactivities[$activitytype][$instance->context->id] = $instance;
+ }
+ } else {
+ $this->courseactivities[$activitytype] = false;
+ }
+ }
+
+ return $this->courseactivities[$activitytype];
+ }
+
+ /**
+ * Returns the course students grades.
+ *
+ * @param array $courseactivities
+ * @return array
+ */
+ public function get_student_grades($courseactivities) {
+
+ if (empty($courseactivities)) {
+ return array();
+ }
+
+ $grades = array();
+ foreach ($courseactivities as $contextid => $instance) {
+ $gradesinfo = grade_get_grades($this->course->id, 'mod', $instance->modname, $instance->instance, $this->studentids);
+
+ // Sort them by activity context and user.
+ if ($gradesinfo && $gradesinfo->items) {
+ foreach ($gradesinfo->items as $gradeitem) {
+ foreach ($gradeitem->grades as $userid => $grade) {
+ if (empty($grades[$contextid][$userid])) {
+ // Initialise it as array because a single activity can have multiple grade items (e.g. workshop).
+ $grades[$contextid][$userid] = array();
+ }
+ $grades[$contextid][$userid][$gradeitem->id] = $grade;
+ }
+ }
+ }
+ }
+
+ return $grades;
+ }
+
+ /**
+ * Guesses all activities that were available during a period of time.
+ *
+ * @param string $activitytype
+