*/**/build/
node_modules/
vendor/
+admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
-admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
lib/ltiprovider/
lib/amd/src/truncate.js
lib/fonts/
+lib/amd/src/adapter.js
lib/validateurlsyntax.php
+lib/amd/src/popper.js
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
theme/more/style/custom.css
node_modules/
vendor/
+admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
-admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
lib/ltiprovider/
lib/amd/src/truncate.js
lib/fonts/
+lib/amd/src/adapter.js
lib/validateurlsyntax.php
+lib/amd/src/popper.js
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'),
'oci' => moodle_database::get_driver_instance('oci', 'native'),
'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver
- 'mssql' => moodle_database::get_driver_instance('mssql', 'native'), // FreeTDS driver
);
foreach ($databases as $type=>$database) {
if ($database->driver_installed() !== true) {
}
}
$CFG->tempdir = $CFG->dataroot.'/temp';
+$CFG->backuptempdir = $CFG->tempdir.'/backup';
$CFG->cachedir = $CFG->dataroot.'/cache';
$CFG->localcachedir = $CFG->dataroot.'/localcache';
cli_error(get_string('pluginschecktodo', 'admin'));
}
+$a = new stdClass();
+$a->oldversion = $oldversion;
+$a->newversion = $newversion;
+
if ($interactive) {
- $a = new stdClass();
- $a->oldversion = $oldversion;
- $a->newversion = $newversion;
echo cli_heading(get_string('databasechecking', '', $a)) . PHP_EOL;
}
// to immediately start browsing the site.
upgrade_themes();
-echo get_string('cliupgradefinished', 'admin')."\n";
+echo get_string('cliupgradefinished', 'admin', $a)."\n";
exit(0); // 0 means success
$temp->add(new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'), new lang_string('configallowuserthemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'), new lang_string('configallowcoursethemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'), new lang_string('configallowcategorythemes', 'admin'), 0));
+ $temp->add(new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'), new lang_string('configallowcohortthemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', new lang_string('allowthemechangeonurl', 'admin'), new lang_string('configallowthemechangeonurl', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'), new lang_string('configallowuserblockhiding', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('allowblockstodock', new lang_string('allowblockstodock', 'admin'), new lang_string('configallowblockstodock', 'admin'), 1));
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
+ $temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
+ new lang_string('forceclean_desc', 'core_admin'), 0));
+
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
$temp->add(new admin_setting_configselect('maxeditingtime', new lang_string('maxeditingtime','admin'), new lang_string('configmaxeditingtime','admin'), 1800,
$choices['1'] = new lang_string('emaildisplayyes');
$choices['2'] = new lang_string('emaildisplaycourse');
$temp->add(new admin_setting_configselect('defaultpreference_maildisplay', new lang_string('emaildisplay'),
- '', 2, $choices));
+ new lang_string('emaildisplay_help'), 2, $choices));
$choices = array();
$choices['0'] = new lang_string('textformat');
$temp->add(new admin_setting_configmultiselect('hiddenuserfields', new lang_string('hiddenuserfields', 'admin'),
new lang_string('confighiddenuserfields', 'admin'), array(),
array('description' => new lang_string('description'),
+ 'email' => new lang_string('email'),
'city' => new lang_string('city'),
'country' => new lang_string('country'),
'timezone' => new lang_string('timezone'),
'native/mariadb' => \moodle_database::get_driver_instance('mariadb', 'native')->get_name(),
'native/pgsql' => \moodle_database::get_driver_instance('pgsql', 'native')->get_name(),
'native/oci' => \moodle_database::get_driver_instance('oci', 'native')->get_name(),
- 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name(),
- 'native/mssql' => \moodle_database::get_driver_instance('mssql', 'native')->get_name()
+ 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name()
);
}
</h2>
<div>{{{framework.description}}}</div>
<h3>{{#str}}competencies, core_competency{{/str}}</h3>
- <div class="row-fluid">
+ <div class="row-fluid row">
<div class="span6 col-lg-6">
<p>
<form data-region="filtercompetencies" data-frameworkid="{{framework.id}}" class="form-inline">
</div>
<div class="span6 card col-lg-6">
- <div class="card-block">
+ <div class="card-block card-body">
<div class="card-title">
<h4 data-region="selected-competency">{{#str}}selectedcompetency, tool_lp{{/str}}</h4>
<span data-region="competencyactionsmenu" class="pull-xs-right">
if (!empty($CFG->antiviruses)) {
mtrace("--> Attempting virus scan of '{$attachment->filename}'");
-
- // Store the file on disk - it will need to be virus scanned first.
- $itemid = rand(1, 999999999);;
- $directory = make_temp_directory("/messageinbound/{$itemid}", false);
- $filepath = $directory . "/" . $attachment->filename;
- if (!$fp = fopen($filepath, "w")) {
- // Unable to open the temporary file to write this to disk.
- mtrace("--> Unable to save the file to disk for virus scanning. Check file permissions.");
-
- throw new \core\message\inbound\processing_failed_exception('attachmentfilepermissionsfailed',
- 'tool_messageinbound');
- }
-
- fwrite($fp, $attachment->content);
- fclose($fp);
-
// Perform a virus scan now.
try {
- \core\antivirus\manager::scan_file($filepath, $attachment->filename, true);
+ \core\antivirus\manager::scan_data($attachment->content);
} catch (\core\antivirus\scanner_exception $e) {
mtrace("--> A virus was found in the attachment '{$attachment->filename}'.");
$this->inform_attachment_virus();
require("$path/db/mobile.php");
foreach ($addons as $addonname => $addoninfo) {
+
+ // Add handlers (for site add-ons).
+ $handlers = !empty($addoninfo['handlers']) ? $addoninfo['handlers'] : array();
+ $handlers = json_encode($handlers); // JSON formatted, since it is a complex structure that may vary over time.
+
+ // Now language strings used by the app.
+ $lang = array();
+ if (!empty($addoninfo['lang'])) {
+ $stringmanager = get_string_manager();
+ $langs = $stringmanager->get_list_of_translations();
+ foreach ($langs as $langid => $langname) {
+ foreach ($addoninfo['lang'] as $stringinfo) {
+ $lang[$langid][$stringinfo[0]] =
+ $stringmanager->get_string($stringinfo[0], $stringinfo[1], null, $langid);
+ }
+ }
+ }
+ $lang = json_encode($lang);
+
$plugininfo = array(
'component' => $component,
'version' => $version,
'dependencies' => !empty($addoninfo['dependencies']) ? $addoninfo['dependencies'] : array(),
'fileurl' => '',
'filehash' => '',
- 'filesize' => 0
+ 'filesize' => 0,
+ 'handlers' => $handlers,
+ 'lang' => $lang,
);
// All the mobile packages must be under the plugin mobile directory.
}
$features = array(
+ 'NoDelegate_CoreOffline' => new lang_string('offlineuse', 'tool_mobile'),
'$mmLoginEmailSignup' => new lang_string('startsignup'),
"$mainmenu" => array(
'$mmSideMenuDelegate_mmCourses' => new lang_string('mycourses'),
'$mmCoursesDelegate_mmaGrades' => new lang_string('grades', 'grades'),
'$mmCoursesDelegate_mmaCourseCompletion' => new lang_string('coursecompletion', 'completion'),
'$mmCoursesDelegate_mmaNotes' => new lang_string('notes', 'notes'),
+ 'NoDelegate_CoreCourseDownload' => new lang_string('downloadcourse', 'tool_mobile'),
+ 'NoDelegate_CoreCoursesDownload' => new lang_string('downloadcourses', 'tool_mobile'),
),
"$user" => array(
'$mmUserDelegate_mmaBadges' => new lang_string('badges', 'badges'),
require_once("$CFG->libdir/externallib.php");
use external_api;
+use external_files;
use external_function_parameters;
use external_value;
use external_single_structure;
use moodle_exception;
use moodle_url;
use core_text;
+use coding_exception;
/**
* This is the external API for this tool.
'fileurl' => new external_value(PARAM_URL, 'The addon package url for download
or empty if it doesn\'t exist.'),
'filehash' => new external_value(PARAM_RAW, 'The addon package hash or empty if it doesn\'t exist.'),
- 'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.')
+ 'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.'),
+ 'handlers' => new external_value(PARAM_RAW, 'Handlers definition (JSON)', VALUE_OPTIONAL),
+ 'lang' => new external_value(PARAM_RAW, 'Language strings used by the handlers (JSON)', VALUE_OPTIONAL),
)
)
),
)
);
}
+
+ /**
+ * Returns description of get_content() parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.5
+ */
+ public static function get_content_parameters() {
+ return new external_function_parameters(
+ array(
+ 'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
+ 'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
+ 'args' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
+ 'value' => new external_value(PARAM_RAW, 'Param value.')
+ )
+ ), 'Args for the method are optional.', VALUE_OPTIONAL
+ )
+ )
+ );
+ }
+
+ /**
+ * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
+ * other structured data that will be used to render a view in the Mobile app..
+ *
+ * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
+ * appropriate security checks to access the information to be returned.
+ *
+ * @param string $component fame of the component.
+ * @param string $method function method name in class \$component\output\mobile.
+ * @param array $args optional arguments for the method.
+ * @return array HTML, JavaScript and other required data and information to create a view in the app.
+ * @since Moodle 3.5
+ * @throws coding_exception
+ */
+ public static function get_content($component, $method, $args = array()) {
+ global $OUTPUT, $PAGE, $USER;
+
+ $params = self::validate_parameters(self::get_content_parameters(),
+ array(
+ 'component' => $component,
+ 'method' => $method,
+ 'args' => $args
+ )
+ );
+
+ // Reformat arguments into something less unwieldy.
+ $arguments = array();
+ foreach ($params['args'] as $paramargument) {
+ $arguments[$paramargument['name']] = $paramargument['value'];
+ }
+
+ // The component was validated via the PARAM_COMPONENT parameter type.
+ $classname = '\\' . $params['component'] .'\output\mobile';
+ if (!method_exists($classname, $params['method'])) {
+ throw new coding_exception("Missing method in $classname");
+ }
+ $result = call_user_func_array(array($classname, $params['method']), array($arguments));
+
+ // Populate otherdata.
+ $otherdata = array();
+ if (!empty($result['otherdata'])) {
+ $result['otherdata'] = (array) $result['otherdata'];
+ foreach ($result['otherdata'] as $name => $value) {
+ $otherdata[] = array(
+ 'name' => $name,
+ 'value' => $value
+ );
+ }
+ }
+
+ return array(
+ 'templates' => !empty($result['templates']) ? $result['templates'] : array(),
+ 'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
+ 'otherdata' => $otherdata,
+ 'files' => !empty($result['files']) ? $result['files'] : array(),
+ 'restrict' => !empty($result['restrict']) ? $result['restrict'] : array(),
+ );
+ }
+
+ /**
+ * Returns description of get_content() result value
+ *
+ * @return array
+ * @since Moodle 3.5
+ */
+ public static function get_content_returns() {
+ return new external_single_structure(
+ array(
+ 'templates' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
+ 'html' => new external_value(PARAM_RAW, 'HTML code.'),
+ )
+ ),
+ 'Templates required by the generated content.'
+ ),
+ 'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
+ 'otherdata' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_RAW, 'Field name.'),
+ 'value' => new external_value(PARAM_RAW, 'Field value.')
+ )
+ ),
+ 'Other data that can be used or manipulated by the template via 2-way data-binding.'
+ ),
+ 'files' => new external_files('Files in the content.'),
+ 'restrict' => new external_single_structure(
+ array(
+ 'users' => new external_multiple_structure(
+ new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
+ ),
+ 'courses' => new external_multiple_structure(
+ new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
+ ),
+ ),
+ 'Restrict this content to certain users or courses.'
+ )
+ )
+ );
+ }
}
Is created only in https sites and is restricted by time and ip address.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- )
+ ),
+ 'tool_mobile_get_content' => array(
+ 'classname' => 'tool_mobile\external',
+ 'methodname' => 'get_content',
+ 'description' => 'Returns a piece of content to be displayed in the Mobile app.',
+ 'type' => 'read',
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+ ),
);
$string['disabledfeatures'] = 'Disabled features';
$string['disabledfeatures_desc'] = 'Select here the features you want to disable in the Mobile app for your site. Please note that some features listed here could be already disabled via other site settings. You will have to log out and log in again in the app to see the changes.';
$string['displayerrorswarning'] = 'Display debug messages (debugdisplay) is enabled. It should be disabled.';
+$string['downloadcourse'] = 'Download course';
+$string['downloadcourses'] = 'Download courses';
$string['enablesmartappbanners'] = 'Enable App Banners';
$string['enablesmartappbanners_desc'] = 'If enabled, a banner promoting the mobile app will be displayed when accessing the site using a mobile browser.';
$string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here; otherwise leave the field empty.';
$string['mobilefeatures'] = 'Mobile features';
$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Manage message outputs.';
$string['mobilesettings'] = 'Mobile settings';
+$string['offlineuse'] = 'Offline use';
$string['pluginname'] = 'Moodle Mobile tools';
+$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
+$string['remoteaddons'] = 'Remote add-ons';
$string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites.';
$string['setuplink'] = 'App download page';
$string['setuplink_desc'] = 'URL of page with links to download the mobile app from the App Store and Google Play.';
$string['smartappbanners'] = 'App Banners';
-$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
-$string['remoteaddons'] = 'Remote add-ons';
$string['typeoflogin'] = 'Type of login';
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
$string['getmoodleonyourmobile'] = 'Get the mobile app';
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php');
use tool_mobile\external;
use tool_mobile\api;
$this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile'));
$result = external::get_autologin_key($token->privatetoken);
}
+
+ /**
+ * Test get_content.
+ */
+ public function test_get_content() {
+
+ $paramval = 16;
+ $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
+ $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
+ $this->assertCount(1, $result['templates']);
+ $this->assertCount(1, $result['otherdata']);
+ $this->assertCount(2, $result['restrict']['users']);
+ $this->assertCount(2, $result['restrict']['courses']);
+ $this->assertEquals('alert();', $result['javascript']);
+ $this->assertEquals('main', $result['templates'][0]['id']);
+ $this->assertEquals('The HTML code', $result['templates'][0]['html']);
+ $this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
+ $this->assertEquals($paramval, $result['otherdata'][0]['value']);
+ $this->assertEquals(array(1, 2), $result['restrict']['users']);
+ $this->assertEquals(array(3, 4), $result['restrict']['courses']);
+ $this->assertEmpty($result['files']);
+ }
+
+ /**
+ * Test get_content non existent function in valid component.
+ */
+ public function test_get_content_non_existent_function() {
+
+ $this->expectException('coding_exception');
+ $result = external::get_content('tool_mobile', 'test_blahblah');
+ }
+
+ /**
+ * Test get_content incorrect component.
+ */
+ public function test_get_content_invalid_component() {
+
+ $this->expectException('moodle_exception');
+ $result = external::get_content('tool_mobile\hack', 'test_view');
+ }
+
+ /**
+ * Test get_content non existent component.
+ */
+ public function test_get_content_non_existent_component() {
+
+ $this->expectException('moodle_exception');
+ $result = external::get_content('tool_blahblahblah', 'test_view');
+ }
}
--- /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/>.
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_mobile\output;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mobile {
+
+ /**
+ * Returns a test view.
+ * @param array $args Arguments from tool_mobile_get_content WS
+ *
+ * @return array HTML, javascript and otherdata
+ */
+ public static function test_view($args) {
+ $args = (object) $args;
+
+ return array(
+ 'templates' => array(
+ array(
+ 'id' => 'main',
+ 'html' => 'The HTML code',
+ ),
+ ),
+ 'javascript' => 'alert();',
+ 'otherdata' => array('otherdata1' => $args->param1),
+ 'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
+ 'files' => array()
+ );
+ }
+}
This files describes changes in tool_mobile code.
Information provided here is intended especially for developers.
+=== 3.5 ===
+
+ * External function tool_mobile::tool_mobile_get_plugins_supporting_mobile now returns additional plugins information required by
+ Moodle Mobile 3.5.0.
+
=== 3.4 ===
* External function tool_mobile::tool_mobile_get_plugins_supporting_mobile is now available via AJAX for not logged users.
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2017110800; // Requires this Moodle version.
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
--- /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/>.
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package tool_monitor
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_monitor\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+use \tool_monitor\subscription_manager;
+use \tool_monitor\rule_manager;
+
+/**
+ * Privacy provider for tool_monitor
+ *
+ * @package tool_monitor
+ * @copyright 2018 Adrian Greeve <adrian@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
+
+ /**
+ * Get information about the user data stored by this plugin.
+ *
+ * @param collection $collection An object for storing metadata.
+ * @return collection The metadata.
+ */
+ public static function get_metadata(collection $collection) : collection {
+ $toolmonitorrules = [
+ 'description' => 'privacy:metadata:description',
+ 'name' => 'privacy:metadata:name',
+ 'userid' => 'privacy:metadata:userid',
+ 'plugin' => 'privacy:metadata:plugin',
+ 'eventname' => 'privacy:metadata:eventname',
+ 'template' => 'privacy:metadata:template',
+ 'frequency' => 'privacy:metadata:frequency',
+ 'timewindow' => 'privacy:metadata:timewindow',
+ 'timemodified' => 'privacy:metadata:timemodifiedrule',
+ 'timecreated' => 'privacy:metadata:timecreatedrule'
+ ];
+ $toolmonitorsubscriptions = [
+ 'userid' => 'privacy:metadata:useridsub',
+ 'timecreated' => 'privacy:metadata:timecreatedsub',
+ 'lastnotificationsent' => 'privacy:metadata:lastnotificationsent',
+ 'inactivedate' => 'privacy:metadata:inactivedate'
+ ];
+ // Tool monitor history doesn't look like it is used at all.
+ $toolmonitorhistory = [
+ 'userid' => 'privacy:metadata:useridhistory',
+ 'timesent' => 'privacy:metadata:timesent'
+ ];
+ $collection->add_database_table('tool_monitor_rules', $toolmonitorrules, 'privacy:metadata:rulessummary');
+ $collection->add_database_table('tool_monitor_subscriptions', $toolmonitorsubscriptions,
+ 'privacy:metadata:subscriptionssummary');
+ $collection->add_database_table('tool_monitor_history', $toolmonitorhistory, 'privacy:metadata:historysummary');
+ $collection->link_subsystem('core_message', 'privacy:metadata:messagesummary');
+ return $collection;
+ }
+
+ /**
+ * Return all contexts for this userid. In this situation the user context.
+ *
+ * @param int $userid The user ID.
+ * @return contextlist The list of context IDs.
+ */
+ public static function get_contexts_for_userid(int $userid) : contextlist {
+ $params = ['useridrules' => $userid, 'useridsubscriptions' => $userid, 'contextuserrule' => CONTEXT_USER,
+ 'contextusersub' => CONTEXT_USER];
+ $sql = "SELECT DISTINCT ctx.id
+ FROM {context} ctx
+ LEFT JOIN {tool_monitor_rules} mr ON ctx.instanceid = mr.userid AND ctx.contextlevel = :contextuserrule
+ LEFT JOIN {tool_monitor_subscriptions} ms ON ctx.instanceid = ms.userid AND ctx.contextlevel = :contextusersub
+ WHERE (ms.userid = :useridrules OR mr.userid = :useridsubscriptions)";
+
+ $contextlist = new contextlist();
+ $contextlist->add_from_sql($sql, $params);
+ return $contextlist;
+ }
+
+ /**
+ * Export all event monitor information for the list of contexts and this user.
+ *
+ * @param approved_contextlist $contextlist The list of approved contexts for a user.
+ */
+ public static function export_user_data(approved_contextlist $contextlist) {
+ global $DB;
+ // Export rules.
+ $context = \context_user::instance($contextlist->get_user()->id);
+ $rules = $DB->get_records('tool_monitor_rules', ['userid' => $contextlist->get_user()->id]);
+ if ($rules) {
+ static::export_monitor_rules($rules, $context);
+ }
+ // Export subscriptions.
+ $subscriptions = subscription_manager::get_user_subscriptions(0, 0, $contextlist->get_user()->id);
+ if ($subscriptions) {
+ static::export_monitor_subscriptions($subscriptions, $context);
+ }
+ }
+
+ /**
+ * Delete all user data for this context.
+ *
+ * @param \context $context The context to delete data for.
+ */
+ public static function delete_data_for_all_users_in_context(\context $context) {
+ // Only delete data for user contexts.
+ if ($context->contextlevel == CONTEXT_USER) {
+ static::delete_user_data($context->instanceid);
+ }
+ }
+
+ /**
+ * Delete all user data for this user only.
+ *
+ * @param approved_contextlist $contextlist The list of approved contexts for a user.
+ */
+ public static function delete_data_for_user(approved_contextlist $contextlist) {
+ static::delete_user_data($contextlist->get_user()->id);
+ }
+
+ /**
+ * This does the deletion of user data for the event monitor.
+ *
+ * @param int $userid The user ID
+ */
+ protected static function delete_user_data(int $userid) {
+ global $DB;
+ // Delete this user's subscriptions first.
+ subscription_manager::delete_user_subscriptions($userid);
+ // Because we only use user contexts the instance ID is the user ID.
+ // Get the rules and check if this user has the capability to delete them.
+ $rules = $DB->get_records('tool_monitor_rules', ['userid' => $userid]);
+ foreach ($rules as $ruledata) {
+ $rule = rule_manager::get_rule($ruledata);
+ // If no-one is suscribed to the rule then it is safe to delete.
+ if ($rule->can_manage_rule($userid) && subscription_manager::count_rule_subscriptions($rule->id) == 0) {
+ $rule->delete_rule();
+ }
+ }
+ }
+
+ /**
+ * This formats and then exports the monitor rules.
+ *
+ * @param array $rules The monitor rules.
+ * @param context_user $context The user context
+ */
+ protected static function export_monitor_rules(array $rules, \context_user $context) {
+ foreach ($rules as $rule) {
+ $rule = rule_manager::get_rule($rule);
+ $ruledata = new \stdClass();
+ $ruledata->name = $rule->name;
+ $ruledata->eventname = $rule->get_event_name();
+ $ruledata->description = $rule->get_description($context);
+ $ruledata->plugin = $rule->get_plugin_name();
+ $ruledata->template = $rule->template;
+ $ruledata->frequency = $rule->get_filters_description();
+ $ruledata->course = $rule->get_course_name($context);
+ $ruledata->timecreated = transform::datetime($rule->timecreated);
+ $ruledata->timemodified = transform::datetime($rule->timemodified);
+ writer::with_context($context)->export_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $rule->name . '_' . $rule->id], $ruledata);
+ }
+ }
+
+ /**
+ * This formats and then exports the event monitor subscriptions.
+ *
+ * @param array $subscriptions Subscriptions
+ * @param \context_user $context The user context
+ */
+ protected static function export_monitor_subscriptions(array $subscriptions, \context_user $context) {
+ foreach ($subscriptions as $subscription) {
+ $subscriptiondata = new \stdClass();
+ $subscriptiondata->instancename = $subscription->get_instance_name();
+ $subscriptiondata->eventname = $subscription->get_event_name();
+ $subscriptiondata->frequency = $subscription->get_filters_description();
+ $subscriptiondata->name = $subscription->get_name($context);
+ $subscriptiondata->description = $subscription->get_description($context);
+ $subscriptiondata->pluginname = $subscription->get_plugin_name();
+ $subscriptiondata->course = $subscription->get_course_name($context);
+ $subscriptiondata->timecreated = transform::datetime($subscription->timecreated);
+ $subscriptiondata->lastnotificationsent = transform::datetime($subscription->lastnotificationsent);
+ writer::with_context($context)->export_data([get_string('privacy:subscriptions', 'tool_monitor'),
+ $subscriptiondata->name . '_' . $subscription->id, $subscriptiondata->course, $subscriptiondata->instancename],
+ $subscriptiondata);
+ }
+ }
+}
}
/**
- * Can the current user manage this rule?
+ * Can the user manage this rule? Defaults to $USER.
*
+ * @param int $userid Check against this userid.
* @return bool true if the current user can manage this rule, else false.
*/
- public function can_manage_rule() {
+ public function can_manage_rule($userid = null) {
$courseid = $this->courseid;
$context = empty($courseid) ? \context_system::instance() : \context_course::instance($this->courseid);
- return has_capability('tool/monitor:managerules', $context);
+ return has_capability('tool/monitor:managerules', $context, $userid);
}
/**
$string['monitor:subscribe'] = 'Subscribe to event monitor rules';
$string['norules'] = 'There are no event monitoring rules.';
$string['pluginname'] = 'Event monitor';
+$string['privacy:createdrules'] = 'Event monitor rules I created';
+$string['privacy:metadata:description'] = 'Description of the rule';
+$string['privacy:metadata:eventname'] = 'Fully qualified name of the event';
+$string['privacy:metadata:frequency'] = 'Frequency of notifications';
+$string['privacy:metadata:historysummary'] = 'Stores the history of the message notifications sent';
+$string['privacy:metadata:inactivedate'] = 'Period of time, in days, after which an inactive subscription will be removed completely';
+$string['privacy:metadata:lastnotificationsent'] = 'When a notification was last sent for this subscription.';
+$string['privacy:metadata:messagesummary'] = 'Notifications are sent to the message system.';
+$string['privacy:metadata:name'] = 'Name of the rule';
+$string['privacy:metadata:plugin'] = 'Frankenstlye name of the plugin';
+$string['privacy:metadata:rulessummary'] = 'This stores monitor rules.';
+$string['privacy:metadata:subscriptionssummary'] = 'Stores user subscriptions to various rules';
+$string['privacy:metadata:template'] = 'Message template';
+$string['privacy:metadata:timecreatedrule'] = 'When this rule was created';
+$string['privacy:metadata:timecreatedsub'] = 'When this subscription was created';
+$string['privacy:metadata:timemodifiedrule'] = 'When this rule was last modified';
+$string['privacy:metadata:timesent'] = 'When the message was sent';
+$string['privacy:metadata:timewindow'] = 'Time window in seconds';
+$string['privacy:metadata:userid'] = 'Id of user who created the rule';
+$string['privacy:metadata:useridhistory'] = 'User to whom this notification was sent';
+$string['privacy:metadata:useridsub'] = 'User id of the subscriber';
+$string['privacy:subscriptions'] = 'My event monitor subscriptions';
$string['processevents'] = 'Process events';
$string['rulename'] = 'Rule name';
$string['ruleareyousure'] = 'Are you sure you want to delete the rule "{$a}"?';
--- /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/>.
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package tool_monitor
+ * @category test
+ * @copyright 2018 Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \tool_monitor\privacy\provider;
+use \core_privacy\local\request\approved_contextlist;
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package tool_monitor
+ * @category test
+ * @copyright 2018 Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_privacy_testcase extends advanced_testcase {
+
+ /**
+ * Set up method.
+ */
+ public function setUp() {
+ $this->resetAfterTest();
+ // Enable monitor.
+ set_config('enablemonitor', 1, 'tool_monitor');
+ }
+
+ /**
+ * Assign a capability to $USER
+ * The function creates a student $USER if $USER->id is empty
+ *
+ * @param string $capability capability name
+ * @param int $contextid
+ * @param int $roleid
+ * @return int the role id - mainly returned for creation, so calling function can reuse it
+ */
+ public static function assign_user_capability($capability, $contextid, $roleid = null) {
+ global $USER;
+
+ // Create a new student $USER if $USER doesn't exist.
+ if (empty($USER->id)) {
+ $user = self::getDataGenerator()->create_user();
+ self::setUser($user);
+ }
+
+ if (empty($roleid)) {
+ $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
+ }
+
+ assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
+
+ role_assign($roleid, $USER->id, $contextid);
+
+ accesslib_clear_all_caches_for_unit_testing();
+
+ return $roleid;
+ }
+
+ /**
+ * Test that a collection with data is returned when calling this function.
+ */
+ public function test_get_metadata() {
+ $collection = new \core_privacy\local\metadata\collection('tool_monitor');
+ $collection = provider::get_metadata($collection);
+ $this->assertNotEmpty($collection);
+ }
+
+ /**
+ * Check that a user context is returned if there is any user data for this user.
+ */
+ public function test_get_contexts_for_userid() {
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+ $this->assertEmpty(provider::get_contexts_for_userid($user2->id));
+
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ // Create a rule with this user.
+ $this->setUser($user);
+ $rule = $monitorgenerator->create_rule();
+ $contextlist = provider::get_contexts_for_userid($user->id);
+
+ // Check that we only get back one context.
+ $this->assertCount(1, $contextlist);
+
+ // Check that a context is returned for just creating a rule.
+ $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]);
+
+ $this->setUser($user2);
+
+ $record = new stdClass();
+ $record->courseid = 0;
+ $record->userid = $user2->id;
+ $record->ruleid = $rule->id;
+
+ $subscription = $monitorgenerator->create_subscription($record);
+ $contextlist = provider::get_contexts_for_userid($user2->id);
+
+ // Check that we only get back one context.
+ $this->assertCount(1, $contextlist);
+
+ // Check that a context is returned for just subscribing to a rule.
+ $this->assertEquals($usercontext2->id, $contextlist->get_contextids()[0]);
+ }
+
+ /**
+ * Test that user data is exported correctly.
+ */
+ public function test_export_user_data() {
+ $user = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ $writer = \core_privacy\local\request\writer::with_context($usercontext);
+ $this->assertFalse($writer->has_any_data());
+
+ $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+ provider::export_user_data($approvedlist);
+
+ // Check that the rules created by this user are exported.
+ $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $rulerecord->name . '_' . $rule->id])->name);
+ $this->assertEquals($secondrulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+ $secondrulerecord->name . '_' . $rule2->id])->name);
+
+ // Check that the subscriptions for this user are also exported.
+ $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:subscriptions', 'tool_monitor'),
+ $rulerecord->name . '_' . $subscription->id, 'Site' , 'All events'])->name);
+ }
+
+ /**
+ * Test deleting all user data for a specific context.
+ */
+ public function test_delete_data_for_all_users_in_context() {
+ global $DB;
+
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ // Need to give user one the ability to manage rules.
+ $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ // Have user 2 subscribe to the second rule created by user 1.
+ $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+ $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+ $this->setUser($user2);
+ $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+ $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+ $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+ $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+ // Try a different context first.
+ provider::delete_data_for_all_users_in_context(context_system::instance());
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // All of the rules should still be present.
+ $this->assertCount(3, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule->id]->userid);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Delete everything for the first user context.
+ provider::delete_data_for_all_users_in_context($usercontext);
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+ $this->assertCount(2, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Get all of the monitor subscriptions.
+ $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+ // There should be two subscriptions left, both for user 2.
+ $this->assertCount(2, $dbsubs);
+ $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+ $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+ }
+
+ /**
+ * This should work identical to the above test.
+ */
+ public function test_delete_data_for_user() {
+ global $DB;
+
+ $user = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ $usercontext = \context_user::instance($user->id);
+ $usercontext2 = \context_user::instance($user2->id);
+ $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+ $this->setUser($user);
+ // Need to give user one the ability to manage rules.
+ $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+ $rulerecord = (object)['name' => 'privacy rule'];
+ $rule = $monitorgenerator->create_rule($rulerecord);
+
+ $secondrulerecord = (object)['name' => 'privacy rule2'];
+ $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+ $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+ $subscription = $monitorgenerator->create_subscription($subscription);
+
+ // Have user 2 subscribe to the second rule created by user 1.
+ $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+ $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+ $this->setUser($user2);
+ $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+ $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+ $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+ $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+ $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+
+ // Delete everything for the first user.
+ provider::delete_data_for_user($approvedlist);
+
+ // Get all of the monitor rules.
+ $dbrules = $DB->get_records('tool_monitor_rules');
+
+ // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+ $this->assertCount(2, $dbrules);
+ $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+ $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+ // Get all of the monitor subscriptions.
+ $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+ // There should be two subscriptions left, both for user 2.
+ $this->assertCount(2, $dbsubs);
+ $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+ $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+ }
+}
--- /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/>.
+
+/**
+ * Accept policies on behalf of users (non-JS version)
+ *
+ * @package tool_policy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(__DIR__.'/../../../config.php');
+require_once($CFG->dirroot.'/user/editlib.php');
+
+$userids = optional_param_array('userids', null, PARAM_INT);
+$versionids = optional_param_array('versionids', null, PARAM_INT);
+$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
+
+require_login();
+if (isguestuser()) {
+ print_error('noguest');
+}
+$context = context_system::instance();
+
+$PAGE->set_context($context);
+$PAGE->set_url(new moodle_url('/admin/tool/policy/accept.php'));
+
+if ($returnurl) {
+ $returnurl = new moodle_url($returnurl);
+} else if (count($userids) == 1) {
+ $userid = reset($userids);
+ $returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $userid]);
+} else {
+ $returnurl = new moodle_url('/admin/tool/policy/acceptances.php');
+}
+// Initialise the form, this will also validate users, versions and check permission to accept policies.
+$form = new \tool_policy\form\accept_policy(null,
+ ['versionids' => $versionids, 'userids' => $userids, 'showbuttons' => true]);
+$form->set_data(['returnurl' => $returnurl]);
+
+if ($form->is_cancelled()) {
+ redirect($returnurl);
+} else if ($form->get_data()) {
+ $form->process();
+ redirect($returnurl);
+}
+
+$output = $PAGE->get_renderer('tool_policy');
+echo $output->header();
+echo $output->heading(get_string('consentdetails', 'tool_policy'));
+$form->display();
+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/>.
+
+/**
+ * View user acceptances to the policies
+ *
+ * @package tool_policy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(__DIR__.'/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+use core\output\notification;
+
+$policyid = optional_param('policyid', null, PARAM_INT);
+$versionid = optional_param('versionid', null, PARAM_INT);
+$versionid = optional_param('versionid', null, PARAM_INT);
+$filtersapplied = optional_param_array('unified-filters', [], PARAM_NOTAGS);
+
+$acceptancesfilter = new \tool_policy\output\acceptances_filter($policyid, $versionid, $filtersapplied);
+$policyid = $acceptancesfilter->get_policy_id_filter();
+$versionid = $acceptancesfilter->get_version_id_filter();
+
+// Set up the page as an admin page 'tool_policy_managedocs'.
+$urlparams = ($policyid ? ['policyid' => $policyid] : []) + ($versionid ? ['versionid' => $versionid] : []);
+admin_externalpage_setup('tool_policy_acceptances', '', $urlparams,
+ new moodle_url('/admin/tool/policy/acceptances.php'));
+
+$acceptancesfilter->validate_ids();
+$output = $PAGE->get_renderer('tool_policy');
+if ($acceptancesfilter->get_versions()) {
+ $acceptances = new \tool_policy\acceptances_table('tool_policy_user_acceptances', $acceptancesfilter, $output);
+ if ($acceptances->is_downloading()) {
+ $acceptances->download();
+ }
+}
+
+echo $output->header();
+echo $output->heading(get_string('useracceptances', 'tool_policy'));
+echo $output->render($acceptancesfilter);
+if (!empty($acceptances)) {
+ $acceptances->display();
+} else if ($acceptancesfilter->get_avaliable_policies()) {
+ // There are no non-guest policies.
+ echo $output->notification(get_string('selectpolicyandversion', 'tool_policy'), notification::NOTIFY_INFO);
+} else {
+ // There are no non-guest policies.
+ echo $output->notification(get_string('nopolicies', 'tool_policy'), notification::NOTIFY_INFO);
+}
+echo $output->footer();
--- /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/>.
+
+/**
+ * Unified filter page JS module for the course participants page.
+ *
+ * @module tool_policy/acceptances_filter
+ * @package tool_policy
+ * @copyright 2017 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'],
+ function($, Autocomplete, Str, Notification) {
+
+ /**
+ * Selectors.
+ *
+ * @access private
+ * @type {{UNIFIED_FILTERS: string}}
+ */
+ var SELECTORS = {
+ UNIFIED_FILTERS: '#unified-filters'
+ };
+
+ /**
+ * Init function.
+ *
+ * @method init
+ * @private
+ */
+ var init = function() {
+ var stringkeys = [{
+ key: 'filterplaceholder',
+ component: 'tool_policy'
+ }, {
+ key: 'nofiltersapplied',
+ component: 'tool_policy'
+ }];
+
+ M.util.js_pending('acceptances_filter_datasource');
+ Str.get_strings(stringkeys).done(function(langstrings) {
+ var placeholder = langstrings[0];
+ var noSelectionString = langstrings[1];
+ Autocomplete.enhance(SELECTORS.UNIFIED_FILTERS, true, 'tool_policy/acceptances_filter_datasource', placeholder,
+ false, true, noSelectionString, true)
+ .then(function() {
+ M.util.js_complete('acceptances_filter_datasource');
+
+ return;
+ })
+ .fail(Notification.exception);
+ }).fail(Notification.exception);
+
+ var last = $(SELECTORS.UNIFIED_FILTERS).val();
+ $(SELECTORS.UNIFIED_FILTERS).on('change', function() {
+ var current = $(this).val();
+ var listoffilters = [];
+ var textfilters = [];
+ var updatedselectedfilters = false;
+
+ $.each(current, function(index, catoption) {
+ var catandoption = catoption.split(':', 2);
+ if (catandoption.length !== 2) {
+ textfilters.push(catoption);
+ return true; // Text search filter.
+ }
+
+ var category = catandoption[0];
+ var option = catandoption[1];
+
+ // The last option (eg. 'Teacher') out of a category (eg. 'Role') in this loop is the one that was last
+ // selected, so we want to use that if there are multiple options from the same category. Eg. The user
+ // may have chosen to filter by the 'Student' role, then wanted to filter by the 'Teacher' role - the
+ // last option in the category to be selected (in this case 'Teacher') will come last, so will overwrite
+ // 'Student' (after this if). We want to let the JS know that the filters have been updated.
+ if (typeof listoffilters[category] !== 'undefined') {
+ updatedselectedfilters = true;
+ }
+
+ listoffilters[category] = option;
+ return true;
+ });
+
+ // Check if we have something to remove from the list of filters.
+ if (updatedselectedfilters) {
+ // Go through and put the list into something we can use to update the list of filters.
+ var updatefilters = [];
+ for (var category in listoffilters) {
+ updatefilters.push(category + ":" + listoffilters[category]);
+ }
+ updatefilters = updatefilters.concat(textfilters);
+ $(this).val(updatefilters);
+ }
+
+ // Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
+ if (last.join(',') != current.join(',')) {
+ this.form.submit();
+ }
+ });
+ };
+
+ /**
+ * Return the unified user filter form.
+ *
+ * @method getForm
+ * @return {DOMElement}
+ */
+ var getForm = function() {
+ return $(SELECTORS.UNIFIED_FILTERS).closest('form');
+ };
+
+ return /** @alias module:core/form-autocomplete */ {
+ /**
+ * Initialise the unified user filter.
+ *
+ * @method init
+ */
+ init: function() {
+ init();
+ },
+
+ /**
+ * Return the unified user filter form.
+ *
+ * @method getForm
+ * @return {DOMElement}
+ */
+ getForm: function() {
+ return getForm();
+ }
+ };
+ });
--- /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/>.
+
+/**
+ * Datasource for the tool_policy/acceptances_filter.
+ *
+ * This module is compatible with core/form-autocomplete.
+ *
+ * @package tool_policy
+ * @copyright 2017 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
+
+ return /** @alias module:tool_policy/acceptances_filter_datasource */ {
+ /**
+ * List filter options.
+ *
+ * @param {String} selector The select element selector.
+ * @param {String} query The query string.
+ * @return {Promise}
+ */
+ list: function(selector, query) {
+ var filteredOptions = [];
+
+ var el = $(selector);
+ var originalOptions = $(selector).data('originaloptionsjson');
+ var selectedFilters = el.val();
+ $.each(originalOptions, function(index, option) {
+ // Skip option if it does not contain the query string.
+ if ($.trim(query) !== '' && option.label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) === -1) {
+ return true;
+ }
+ // Skip filters that have already been selected.
+ if ($.inArray(option.value, selectedFilters) > -1) {
+ return true;
+ }
+
+ filteredOptions.push(option);
+ return true;
+ });
+
+ var deferred = new $.Deferred();
+ deferred.resolve(filteredOptions);
+
+ return deferred.promise();
+ },
+
+ /**
+ * Process the results for auto complete elements.
+ *
+ * @param {String} selector The selector of the auto complete element.
+ * @param {Array} results An array or results.
+ * @return {Array} New array of results.
+ */
+ processResults: function(selector, results) {
+ var options = [];
+ $.each(results, function(index, data) {
+ options.push({
+ value: data.value,
+ label: data.label
+ });
+ });
+ return options;
+ },
+
+ /**
+ * Source of data for Ajax element.
+ *
+ * @param {String} selector The selector of the auto complete element.
+ * @param {String} query The query string.
+ * @param {Function} callback A callback function receiving an array of results.
+ */
+ /* eslint-disable promise/no-callback-in-promise */
+ transport: function(selector, query, callback) {
+ this.list(selector, query).then(callback).catch(Notification.exception);
+ }
+ };
+
+});
--- /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/>.
+
+/**
+ * Add policy consent modal to the page
+ *
+ * @module tool_policy/acceptmodal
+ * @class AcceptOnBehalf
+ * @package tool_policy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/notification', 'core/fragment',
+ 'core/ajax', 'core/yui'],
+ function($, Str, ModalFactory, ModalEvents, Notification, Fragment, Ajax, Y) {
+
+ "use strict";
+
+ /**
+ * Constructor
+ *
+ * @param {int} contextid
+ *
+ * Each call to init gets it's own instance of this class.
+ */
+ var AcceptOnBehalf = function(contextid) {
+ this.contextid = contextid;
+ this.init();
+ };
+
+ /**
+ * @var {Modal} modal
+ * @private
+ */
+ AcceptOnBehalf.prototype.modal = null;
+
+ /**
+ * @var {int} contextid
+ * @private
+ */
+ AcceptOnBehalf.prototype.contextid = -1;
+
+ /**
+ * @var {Array} strings
+ * @private
+ */
+ AcceptOnBehalf.prototype.stringKeys = [
+ {
+ key: 'consentdetails',
+ component: 'tool_policy'
+ },
+ {
+ key: 'iagreetothepolicy',
+ component: 'tool_policy'
+ },
+ {
+ key: 'selectusersforconsent',
+ component: 'tool_policy'
+ },
+ {
+ key: 'ok'
+ }
+ ];
+
+ /**
+ * Initialise the class.
+ *
+ * @private
+ */
+ AcceptOnBehalf.prototype.init = function() {
+ // Initialise for links accepting policies for individual users.
+ var triggers = $('a[data-action=acceptmodal]');
+ triggers.on('click', function(e) {
+ e.preventDefault();
+ var href = $(e.currentTarget).attr('href'),
+ formData = href.slice(href.indexOf('?') + 1);
+ this.showFormModal(formData);
+ }.bind(this));
+
+ // Initialise for multiple users acceptance form.
+ triggers = $('form[data-action=acceptmodal]');
+ triggers.on('submit', function(e) {
+ e.preventDefault();
+ if ($(e.currentTarget).find('input[type=checkbox][name="userids[]"]:checked').length) {
+ var formData = $(e.currentTarget).serialize();
+ this.showFormModal(formData, triggers);
+ } else {
+ Str.get_strings(this.stringKeys).done(function(strings) {
+ Notification.alert('', strings[2], strings[3]);
+ });
+ }
+ }.bind(this));
+ };
+
+ /**
+ * Show modal with a form
+ *
+ * @param {String} formData
+ * @param {object} triggerElement The trigger HTML jQuery object
+ */
+ AcceptOnBehalf.prototype.showFormModal = function(formData, triggerElement) {
+ // Fetch the title string.
+ Str.get_strings(this.stringKeys).done(function(strings) {
+ // Create the modal.
+ ModalFactory.create({
+ type: ModalFactory.types.SAVE_CANCEL,
+ title: strings[0],
+ body: ''
+ }, triggerElement).done(function(modal) {
+ this.modal = modal;
+ this.setupFormModal(formData, strings[1]);
+ }.bind(this));
+ }.bind(this))
+ .fail(Notification.exception);
+ };
+
+ /**
+ * Setup form inside a modal
+ *
+ * @param {String} formData
+ * @param {String} saveText
+ */
+ AcceptOnBehalf.prototype.setupFormModal = function(formData, saveText) {
+ var modal = this.modal;
+
+ modal.setLarge();
+
+ modal.setSaveButtonText(saveText);
+
+ // We want to reset the form every time it is opened.
+ modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
+
+ modal.setBody(this.getBody(formData));
+
+ // We catch the modal save event, and use it to submit the form inside the modal.
+ // Triggering a form submission will give JS validation scripts a chance to check for errors.
+ modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
+ // We also catch the form submit event and use it to submit the form with ajax.
+ modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
+
+ modal.show();
+ };
+
+ /**
+ * Load the body of the modal (contains the form)
+ *
+ * @method getBody
+ * @private
+ * @param {String} formData
+ * @return {Promise}
+ */
+ AcceptOnBehalf.prototype.getBody = function(formData) {
+ if (typeof formData === "undefined") {
+ formData = {};
+ }
+ // Get the content of the modal.
+ var params = {jsonformdata: JSON.stringify(formData)};
+ return Fragment.loadFragment('tool_policy', 'accept_on_behalf', this.contextid, params);
+ };
+
+ /**
+ * Submit the form inside the modal via AJAX request
+ *
+ * @method submitFormAjax
+ * @private
+ * @param {Event} e Form submission event.
+ */
+ AcceptOnBehalf.prototype.submitFormAjax = function(e) {
+ // We don't want to do a real form submission.
+ e.preventDefault();
+
+ // Convert all the form elements values to a serialised string.
+ var formData = this.modal.getRoot().find('form').serialize();
+
+ var requests = Ajax.call([{
+ methodname: 'tool_policy_submit_accept_on_behalf',
+ args: {jsonformdata: JSON.stringify(formData)}
+ }]);
+ requests[0].done(function(data) {
+ if (data.validationerrors) {
+ this.modal.setBody(this.getBody(formData));
+ } else {
+ this.close();
+ }
+ }.bind(this)).fail(Notification.exception);
+ };
+
+ /**
+ * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
+ *
+ * @method submitForm
+ * @param {Event} e Form submission event.
+ * @private
+ */
+ AcceptOnBehalf.prototype.submitForm = function(e) {
+ e.preventDefault();
+ this.modal.getRoot().find('form').submit();
+ };
+
+ /**
+ * Close the modal
+ */
+ AcceptOnBehalf.prototype.close = function() {
+ this.destroy();
+ document.location.reload();
+ };
+
+ /**
+ * Destroy the modal
+ */
+ AcceptOnBehalf.prototype.destroy = function() {
+ Y.use('moodle-core-formchangechecker', function() {
+ M.core_formchangechecker.reset_form_dirty_state();
+ });
+ this.modal.destroy();
+ };
+
+ return /** @alias module:tool_policy/acceptmodal */ {
+ // Public variables and functions.
+ /**
+ * Attach event listeners to initialise this module.
+ *
+ * @method init
+ * @param {int} contextid The contextid for the course.
+ * @return {AcceptOnBehalf}
+ */
+ getInstance: function(contextid) {
+ return new AcceptOnBehalf(contextid);
+ }
+ };
+ });
--- /dev/null
+/**\r
+ * \r
+ * JQUERY EU COOKIE LAW POPUPS\r
+ * version 1.0.1\r
+ * \r
+ * Code on Github:\r
+ * https://github.com/wimagguc/jquery-eu-cookie-law-popup\r
+ * \r
+ * To see a live demo, go to:\r
+ * http://www.wimagguc.com/2015/03/jquery-eu-cookie-law-popup/\r
+ * \r
+ * by Richard Dancsi\r
+ * http://www.wimagguc.com/\r
+ * \r
+ */\r
+\r
+define(\r
+['jquery'],\r
+function($) {\r
+\r
+// for ie9 doesn't support debug console >>>\r
+if (!window.console) window.console = {};\r
+if (!window.console.log) window.console.log = function () { };\r
+// ^^^\r
+\r
+$.fn.euCookieLawPopup = (function() {\r
+\r
+ var _self = this;\r
+\r
+ ///////////////////////////////////////////////////////////////////////////////////////////////\r
+ // PARAMETERS (MODIFY THIS PART) //////////////////////////////////////////////////////////////\r
+ _self.params = {\r
+ cookiePolicyUrl : 'http://www.wimagguc.com/?cookie-policy',\r
+ popupPosition : 'top',\r
+ colorStyle : 'default',\r
+ compactStyle : false,\r
+ popupTitle : 'This website is using cookies',\r
+ popupText : 'We use cookies to ensure that we give you the best experience on our website. If you continue without changing your settings, we\'ll assume that you are happy to receive all cookies on this website.',\r
+ buttonContinueTitle : 'Continue',\r
+ buttonLearnmoreTitle : 'Learn more',\r
+ buttonLearnmoreOpenInNewWindow : true,\r
+ agreementExpiresInDays : 30,\r
+ autoAcceptCookiePolicy : false,\r
+ htmlMarkup : null\r
+ };\r
+\r
+ ///////////////////////////////////////////////////////////////////////////////////////////////\r
+ // VARIABLES USED BY THE FUNCTION (DON'T MODIFY THIS PART) ////////////////////////////////////\r
+ _self.vars = {\r
+ INITIALISED : false,\r
+ HTML_MARKUP : null,\r
+ COOKIE_NAME : 'EU_COOKIE_LAW_CONSENT'\r
+ };\r
+\r
+ ///////////////////////////////////////////////////////////////////////////////////////////////\r
+ // PRIVATE FUNCTIONS FOR MANIPULATING DATA ////////////////////////////////////////////////////\r
+\r
+ // Overwrite default parameters if any of those is present\r
+ var parseParameters = function(object, markup, settings) {\r
+\r
+ if (object) {\r
+ var className = $(object).attr('class') ? $(object).attr('class') : '';\r
+ if (className.indexOf('eupopup-top') > -1) {\r
+ _self.params.popupPosition = 'top';\r
+ }\r
+ else if (className.indexOf('eupopup-fixedtop') > -1) {\r
+ _self.params.popupPosition = 'fixedtop';\r
+ }\r
+ else if (className.indexOf('eupopup-bottomright') > -1) {\r
+ _self.params.popupPosition = 'bottomright';\r
+ }\r
+ else if (className.indexOf('eupopup-bottomleft') > -1) {\r
+ _self.params.popupPosition = 'bottomleft';\r
+ }\r
+ else if (className.indexOf('eupopup-bottom') > -1) {\r
+ _self.params.popupPosition = 'bottom';\r
+ }\r
+ else if (className.indexOf('eupopup-block') > -1) {\r
+ _self.params.popupPosition = 'block';\r
+ }\r
+ if (className.indexOf('eupopup-color-default') > -1) {\r
+ _self.params.colorStyle = 'default';\r
+ }\r
+ else if (className.indexOf('eupopup-color-inverse') > -1) {\r
+ _self.params.colorStyle = 'inverse';\r
+ }\r
+ if (className.indexOf('eupopup-style-compact') > -1) {\r
+ _self.params.compactStyle = true;\r
+ }\r
+ }\r
+\r
+ if (markup) {\r
+ _self.params.htmlMarkup = markup;\r
+ }\r
+\r
+ if (settings) {\r
+ if (typeof settings.cookiePolicyUrl !== 'undefined') {\r
+ _self.params.cookiePolicyUrl = settings.cookiePolicyUrl;\r
+ }\r
+ if (typeof settings.popupPosition !== 'undefined') {\r
+ _self.params.popupPosition = settings.popupPosition;\r
+ }\r
+ if (typeof settings.colorStyle !== 'undefined') {\r
+ _self.params.colorStyle = settings.colorStyle;\r
+ }\r
+ if (typeof settings.popupTitle !== 'undefined') {\r
+ _self.params.popupTitle = settings.popupTitle;\r
+ }\r
+ if (typeof settings.popupText !== 'undefined') {\r
+ _self.params.popupText = settings.popupText;\r
+ }\r
+ if (typeof settings.buttonContinueTitle !== 'undefined') {\r
+ _self.params.buttonContinueTitle = settings.buttonContinueTitle;\r
+ }\r
+ if (typeof settings.buttonLearnmoreTitle !== 'undefined') {\r
+ _self.params.buttonLearnmoreTitle = settings.buttonLearnmoreTitle;\r
+ }\r
+ if (typeof settings.buttonLearnmoreOpenInNewWindow !== 'undefined') {\r
+ _self.params.buttonLearnmoreOpenInNewWindow = settings.buttonLearnmoreOpenInNewWindow;\r
+ }\r
+ if (typeof settings.agreementExpiresInDays !== 'undefined') {\r
+ _self.params.agreementExpiresInDays = settings.agreementExpiresInDays;\r
+ }\r
+ if (typeof settings.autoAcceptCookiePolicy !== 'undefined') {\r
+ _self.params.autoAcceptCookiePolicy = settings.autoAcceptCookiePolicy;\r
+ }\r
+ if (typeof settings.htmlMarkup !== 'undefined') {\r
+ _self.params.htmlMarkup = settings.htmlMarkup;\r
+ }\r
+ }\r
+\r
+ };\r
+\r
+ var createHtmlMarkup = function() {\r
+\r
+ if (_self.params.htmlMarkup) {\r