grunt ;
# Add all files to the git index and then run diff --cached to see all changes.
# This ensures that we get the status of all files, including new files.
+ # We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
git add . ;
+ git reset -- npm-shrinkwrap.json ;
git diff --cached --exit-code ;
fi
'non-interactive' => false,
'allow-unstable' => false,
'help' => false,
- 'lang' => $lang
+ 'lang' => $lang,
+ 'verbose-settings' => false
),
array(
'h' => 'help'
site language if not set. Defaults to 'en' if the lang
parameter is invalid or if the language pack is not
installed.
+--verbose-settings Show new settings values. By default only the name of
+ new core or plugin settings are displayed. This option
+ outputs the new values as well as the setting name.
-h, --help Print out this help
Example:
// log in as admin - we need doanything permission when applying defaults
\core\session\manager::set_user(get_admin());
-// apply all default settings, just in case do it twice to fill all defaults
-admin_apply_default_settings(NULL, false);
-admin_apply_default_settings(NULL, false);
+// Apply default settings and output those that have changed.
+cli_heading(get_string('cliupgradedefaultheading', 'admin'));
+$settingsoutput = admin_apply_default_settings(null, false);
+
+foreach ($settingsoutput as $setting => $value) {
+
+ if ($options['verbose-settings']) {
+ $stringvlaues = array(
+ 'name' => $setting,
+ 'defaultsetting' => var_export($value, true) // Expand objects.
+ );
+ echo get_string('cliupgradedefaultverbose', 'admin', $stringvlaues) . PHP_EOL;
+
+ } else {
+ echo get_string('cliupgradedefault', 'admin', $setting) . PHP_EOL;
+
+ }
+}
// This needs to happen at the end to ensure it occurs after all caches
// have been purged for the last time.
<VENDOR name="oracle" version="10.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
+ <RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<VENDOR name="oracle" version="10.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
+ <RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
$action = optional_param('action', '', PARAM_ALPHA);
$filterpath = optional_param('filterpath', '', PARAM_PLUGIN);
-require_login();
-$systemcontext = context_system::instance();
-require_capability('moodle/site:config', $systemcontext);
-
admin_externalpage_setup('managefilters');
// Clean up bogus filter states first.
// This is an admin page
admin_externalpage_setup('managemessageoutputs');
-// Require site configuration capability
-require_capability('moodle/site:config', context_system::instance());
-
// Get the submitted params
$disable = optional_param('disable', 0, PARAM_INT);
$enable = optional_param('enable', 0, PARAM_INT);
$perpage = optional_param('perpage', 30, PARAM_INT);
$action = trim(strtolower(optional_param('action', '', PARAM_ALPHA)));
-require_login();
-
admin_externalpage_setup('ssoaccesscontrol');
if (!extension_loaded('openssl')) {
$hostid = required_param('hostid', PARAM_INT);
-require_login();
$context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
$mnet = get_mnet_environment();
require_once($CFG->libdir.'/adminlib.php');
include_once($CFG->dirroot.'/mnet/lib.php');
- require_login();
admin_externalpage_setup('net');
$context = context_system::instance();
- require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
$site = get_site();
$mnet = get_mnet_environment();
require_once($CFG->dirroot.'/mnet/lib.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/mnet/peer_forms.php');
-require_login();
-
-$context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, 'nopermissions');
/// Initialize variables.
$hostid = optional_param('hostid', 0, PARAM_INT);
require_once($CFG->dirroot . '/' . $CFG->admin .'/mnet/profilefields_form.php');
$mnet = get_mnet_environment();
-require_login();
$hostid = required_param('hostid', PARAM_INT);
$mnet_peer = new mnet_peer();
$mnet_peer->set_id($hostid);
-$context = context_system::instance();
-
-require_capability('moodle/site:config', $context, $USER->id, true, 'nopermissions');
admin_externalpage_setup('mnetpeers');
$form = new mnet_profile_form(null, array('hostid' => $hostid));
require_once($CFG->dirroot . '/' . $CFG->admin . '/mnet/services_form.php');
$mnet = get_mnet_environment();
-require_login();
admin_externalpage_setup('mnetpeers');
-$context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
$hostid = required_param('hostid', PARAM_INT);
print_error('mnetdisabled', 'mnet');
}
-require_login();
admin_externalpage_setup('mnettestclient');
-$context = context_system::instance();
-require_capability('moodle/site:config', $context);
-
error_reporting(DEBUG_ALL);
echo $OUTPUT->header();
require_once($CFG->libdir.'/adminlib.php');
include_once($CFG->dirroot.'/mnet/lib.php');
- require_login();
admin_externalpage_setup('trustedhosts');
- $context = context_system::instance();
-
- require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
if (!extension_loaded('openssl')) {
echo $OUTPUT->header();
admin_externalpage_setup($pagename);
-require_capability('moodle/site:config', context_system::instance());
-
$baseurl = "$CFG->wwwroot/$CFG->admin/portfolio.php";
$sesskeyurl = "$CFG->wwwroot/$CFG->admin/portfolio.php?sesskey=" . sesskey();
$configstr = get_string('manageportfolios', 'portfolio');
require_once($CFG->libdir . '/tablelib.php');
// Check permissions.
-require_login();
$systemcontext = context_system::instance();
require_capability('moodle/question:config', $systemcontext);
$canviewreports = has_capability('report/questioninstances:view', $systemcontext);
$visible = false;
}
-require_capability('moodle/site:config', context_system::instance());
admin_externalpage_setup($pagename);
$sesskeyurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php?sesskey=' . sesskey();
}
admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstance.php'));
-require_capability('moodle/site:config', $context);
$baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey()));
if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_sesskey()) {
$controller->process_submission();
- $syscontext->mark_dirty();
$event = null;
// Create event depending on mode.
switch ($mode) {
assign_capability($changedcap, $this->permissions[$changedcap],
$this->roleid, $this->context->id, true);
}
-
- // Force accessinfo refresh for users visiting this context.
- $this->context->mark_dirty();
}
public function display() {
// Check access permissions.
$systemcontext = context_system::instance();
-require_login();
require_capability('moodle/role:manage', $systemcontext);
admin_externalpage_setup('defineroles', '', array('action' => $action, 'roleid' => $roleid), new moodle_url('/admin/roles/define.php'));
// Check access permissions.
$systemcontext = context_system::instance();
-require_login();
require_capability('moodle/role:manage', $systemcontext);
admin_externalpage_setup('defineroles');
die;
}
if (!delete_role($roleid)) {
- // The delete failed, but mark the context dirty in case.
- $systemcontext->mark_dirty();
+ // The delete failed.
print_error('cannotdeleterolewithid', 'error', $baseurl, $roleid);
}
// Deleted a role sitewide...
- $systemcontext->mark_dirty();
redirect($baseurl);
break;
$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)
+ new lang_string('defaultpredictionsprocessor', 'analytics'), new lang_string('predictionsprocessor_help', 'analytics'),
+ \core_analytics\manager::default_mlbackend(), $predictors)
);
// Log store.
'customusermenuitems',
new lang_string('customusermenuitems', 'admin'),
new lang_string('configcustomusermenuitems', 'admin'),
- 'grades,grades|/grade/report/mygrades.php|grades
-messages,message|/message/index.php|message
-preferences,moodle|/user/preferences.php|preferences',
+ 'grades,grades|/grade/report/mygrades.php|t/grades
+messages,message|/message/index.php|t/message
+preferences,moodle|/user/preferences.php|t/preferences',
PARAM_RAW,
'50',
'10'
// coursecontact is the person responsible for course - usually manages enrolments, receives notification, etc.
$temp = new admin_settingpage('coursecontact', new lang_string('courses'));
$temp->add(new admin_setting_special_coursecontact());
+ $temp->add(new admin_setting_configcheckbox('coursecontactduplicates',
+ new lang_string('coursecontactduplicates', 'admin'),
+ new lang_string('coursecontactduplicates_desc', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('courselistshortnames',
new lang_string('courselistshortnames', 'admin'),
new lang_string('courselistshortnames_desc', 'admin'), 0));
new lang_string('configcourseoverviewfileslimit', 'admin'), 1, PARAM_INT));
$temp->add(new admin_setting_configtext('courseoverviewfilesext', new lang_string('courseoverviewfilesext'),
new lang_string('configcourseoverviewfilesext', 'admin'), '.jpg,.gif,.png'));
+ $temp->add(new admin_setting_configtext('coursegraceperiodbefore', new lang_string('coursegraceperiodbefore', 'admin'),
+ new lang_string('configcoursegraceperiodbefore', 'admin'), 0, PARAM_INT));
+ $temp->add(new admin_setting_configtext('coursegraceperiodafter', new lang_string('coursegraceperiodafter', 'admin'),
+ new lang_string('configcoursegraceperiodafter', 'admin'), 0, PARAM_INT));
$ADMIN->add('appearance', $temp);
$temp = new admin_settingpage('ajax', new lang_string('ajaxuse'));
$ADMIN->add('appearance', $temp);
} // end of speedup
-
array('moodle/category:manage')
)
);
+ $ADMIN->add('courses',
+ new admin_externalpage('addnewcourse', new lang_string('addnewcourse'),
+ new moodle_url('/course/edit.php', array('category' => 0)),
+ array('moodle/category:manage')
+ )
+ );
$ADMIN->add('courses',
new admin_externalpage('restorecourse', new lang_string('restorecourse', 'admin'),
new moodle_url('/backup/restorefile.php', array('contextid' => context_system::instance()->id)),
--- /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 gives information about Moodle Services
+ *
+ * @package core
+ * @copyright 2018 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+
+ // Create Moodle Services information.
+ $moodleservices->add(new admin_setting_heading('moodleservicesintro', '',
+ new lang_string('moodleservices_help', 'admin')));
+
+ // Moodle Partners information.
+ if (empty($CFG->disableserviceads_partner)) {
+ $moodleservices->add(new admin_setting_heading('moodlepartners',
+ new lang_string('moodlepartners', 'admin'),
+ new lang_string('moodlepartners_help', 'admin')));
+ }
+
+ // Moodle app information.
+ $moodleservices->add(new admin_setting_heading('moodleapp',
+ new lang_string('moodleapp', 'admin'),
+ new lang_string('moodleapp_help', 'admin')));
+
+ // Branded Moodle app information.
+ if (empty($CFG->disableserviceads_branded)) {
+ $moodleservices->add(new admin_setting_heading('moodlebrandedapp',
+ new lang_string('moodlebrandedapp', 'admin'),
+ new lang_string('moodlebrandedapp_help', 'admin')));
+ }
+}
+
+
// "systempaths" settingpage
$temp = new admin_settingpage('systempaths', new lang_string('systempaths','admin'));
-
+$temp->add(new admin_setting_configexecutable('pathtophp', new lang_string('pathtophp', 'admin'),
+ new lang_string('configpathtophp', 'admin'), ''));
$temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'), new lang_string('configpathtodu', 'admin'), ''));
$temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
$temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
// hidden upgrade script
$ADMIN->add('root', new admin_externalpage('upgradesettings', new lang_string('upgradesettings', 'admin'), "$CFG->wwwroot/$CFG->admin/upgradesettings.php", 'moodle/site:config', true));
+// Adding Moodle Services information page.
+$moodleservices = new admin_settingpage('moodleservices', new lang_string('moodleservices',
+ 'admin'));
+$ADMIN->add('root', $moodleservices);
+
if ($hassiteconfig) {
$optionalsubsystems = new admin_settingpage('optionalsubsystems', new lang_string('advancedfeatures', 'admin'));
$ADMIN->add('root', $optionalsubsystems);
--- /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 core_admin/setting_description
+
+ Admin setting description template.
+
+ Context variables required for this template:
+ * labelfor - id of the form element
+ * title - Setting title
+ * name - Setting name
+
+ Example context (json):
+ {
+ "title": "Setting title",
+ "name": "Name",
+ "description": "Description goes here"
+ }
+}}
+{{!
+ Setting description.
+}}
+<div class="form-item form-horizontal clearfix">
+ <div class="form-label">
+ <label>
+ {{{title}}}
+ </label>
+ <span class="form-shortname ">{{{name}}}</span>
+ </div>
+ <div class="controls felement fstatic">{{{description}}}</div>
+</div>
\ No newline at end of file
Context variables required for this template:
* actionurl - Url to post to
* hasresults - True if there are results
- * results - List of results containing url, title, settings (array of raw html)
+ * results - List of results containing url, title, path (array of strings), settings (array of raw html)
* showsave - Show save buttons
Example context (json):
"actionurl": "/",
"hasresults": true,
"results": [
- { "url": "/", "title": "Match!", "settings": [ "blah blah blah" ] }
+ { "url": "/", "title": "Match!", "path": ["Administration", "Match!"], "settings": [ "blah blah blah" ] }
]
}
}}
</div>
<fieldset>
<div class="clearer"></div>
+ <h2 class="main">{{#str}}searchresults, admin{{/str}}</h2>
{{#hasresults}}
{{#results}}
- <h2 class="main">{{#str}}searchresults, admin{{/str}} - <a href="{{url}}">{{{title}}}</a></h2>
+ <h3 class="adminpagetitle"><a href="{{url}}">{{{title}}}</a></h3>
+ <ul class="adminpagepath" aria-label="{{#str}} pagepath, core {{/str}}">
+ {{#path}}
+ <li>{{.}}</li>
+ {{/path}}
+ </ul>
<fieldset class="adminsettings">
{{#settings}}
<div class="clearer"></div>
$mform->addElement('select', 'timesplitting', get_string('timesplittingmethod', 'analytics'), $timesplittings);
$mform->addHelpButton('timesplitting', 'timesplittingmethod', 'analytics');
+ $defaultprocessor = \core_analytics\manager::get_predictions_processor_name(
+ \core_analytics\manager::get_predictions_processor()
+ );
+ $predictionprocessors = ['' => get_string('defaultpredictoroption', 'analytics', $defaultprocessor)];
+ foreach ($this->_customdata['predictionprocessors'] as $classname => $predictionsprocessor) {
+ if ($predictionsprocessor->is_ready() !== true) {
+ continue;
+ }
+ $optionname = \tool_analytics\output\helper::class_to_option($classname);
+ $predictionprocessors[$optionname] = \core_analytics\manager::get_predictions_processor_name($predictionsprocessor);
+ }
+
+ $mform->addElement('select', 'predictionsprocessor', get_string('predictionsprocessor', 'analytics'),
+ $predictionprocessors);
+ $mform->addHelpButton('predictionsprocessor', 'predictionsprocessor', 'analytics');
+
$mform->addElement('hidden', 'id', $this->_customdata['id']);
$mform->setType('id', PARAM_INT);
'id' => $model->get_id(),
'model' => $model,
'indicators' => $model->get_potential_indicators(),
- 'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods()
+ 'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods(),
+ 'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors()
);
$mform = new \tool_analytics\output\form\edit_model(null, $customdata);
$indicators[] = \core_analytics\manager::get_indicator($indicatorclass);
}
$timesplitting = \tool_analytics\output\helper::option_to_class($data->timesplitting);
- $model->update($data->enabled, $indicators, $timesplitting);
+ $predictionsprocessor = \tool_analytics\output\helper::option_to_class($data->predictionsprocessor);
+ $model->update($data->enabled, $indicators, $timesplitting, $predictionsprocessor);
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
}
$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);
+ $modelobj->predictionsprocessor = \tool_analytics\output\helper::class_to_option($modelobj->predictionsprocessor);
$mform->set_data($modelobj);
$mform->display();
break;
if (self::is_site_dpo($requestinguser)) {
// The user making the request is a DPO. Should be fine.
$datarequest->set('dpo', $requestinguser);
- } else {
- // If not a DPO, only users with the capability to make data requests for the user should be allowed.
- // (e.g. users with the Parent role, etc).
- if (!self::can_create_data_request_for_user($foruser)) {
- $forusercontext = \context_user::instance($foruser);
- throw new required_capability_exception($forusercontext,
- 'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
- }
}
}
// The user making the request.
/**
* Checks whether a non-DPO user can make a data request for another user.
*
- * @param int $user The user ID of the target user.
- * @param int $requester The user ID of the user making the request.
- * @return bool
- * @throws coding_exception
+ * @param int $user The user ID of the target user.
+ * @param int $requester The user ID of the user making the request.
+ * @return bool
*/
public static function can_create_data_request_for_user($user, $requester = null) {
$usercontext = \context_user::instance($user);
+
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
}
+ /**
+ * Require that the current user can make a data request for the specified other user.
+ *
+ * @param int $user The user ID of the target user.
+ * @param int $requester The user ID of the user making the request.
+ * @return bool
+ */
+ public static function require_can_create_data_request_for_user($user, $requester = null) {
+ $usercontext = \context_user::instance($user);
+
+ require_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
+
+ return true;
+ }
+
/**
* Checks whether a user can download a data request.
*
* @return \tool_dataprivacy\purpose.
*/
public static function create_purpose(stdClass $record) {
- self::check_can_manage_data_registry();
-
$purpose = new purpose(0, $record);
$purpose->create();
* @return \tool_dataprivacy\purpose.
*/
public static function update_purpose(stdClass $record) {
- self::check_can_manage_data_registry();
-
if (!isset($record->sensitivedatareasons)) {
$record->sensitivedatareasons = '';
}
* @return bool
*/
public static function delete_purpose($id) {
- self::check_can_manage_data_registry();
-
$purpose = new purpose($id);
if ($purpose->is_used()) {
throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
* @return \tool_dataprivacy\purpose[]
*/
public static function get_purposes() {
- self::check_can_manage_data_registry();
-
return purpose::get_records([], 'name', 'ASC');
}
* @return \tool_dataprivacy\category.
*/
public static function create_category(stdClass $record) {
- self::check_can_manage_data_registry();
-
$category = new category(0, $record);
$category->create();
* @return \tool_dataprivacy\category.
*/
public static function update_category(stdClass $record) {
- self::check_can_manage_data_registry();
-
$category = new category($record->id);
$category->from_record($record);
* @return bool
*/
public static function delete_category($id) {
- self::check_can_manage_data_registry();
-
$category = new category($id);
if ($category->is_used()) {
throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
* @return \tool_dataprivacy\category[]
*/
public static function get_categories() {
- self::check_can_manage_data_registry();
-
return category::get_records([], 'name', 'ASC');
}
* @return \tool_dataprivacy\context_instance
*/
public static function set_context_instance($record) {
- self::check_can_manage_data_registry($record->contextid);
-
if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
// Update.
$instance->from_record($record);
* @return null
*/
public static function unset_context_instance(context_instance $instance) {
- self::check_can_manage_data_registry($instance->get('contextid'));
$instance->delete();
}
public static function set_contextlevel($record) {
global $DB;
- // Only manager at system level can set this.
- self::check_can_manage_data_registry();
-
if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
throw new \coding_exception('Only context system and context user can set a contextlevel ' .
'purpose and retention');
* @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false
*/
- public static function get_effective_context_category(\context $context, $forcedvalue=false) {
- self::check_can_manage_data_registry($context->id);
+ public static function get_effective_context_category(\context $context, $forcedvalue = false) {
if (!data_registry::defaults_set()) {
return false;
}
* @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
* @return purpose|false
*/
- public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
- self::check_can_manage_data_registry($context->id);
+ public static function get_effective_context_purpose(\context $context, $forcedvalue = false) {
if (!data_registry::defaults_set()) {
return false;
}
* Returns the effective category given a context level.
*
* @param int $contextlevel
- * @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false
*/
- public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
- self::check_can_manage_data_registry(\context_system::instance()->id);
+ public static function get_effective_contextlevel_category($contextlevel) {
if (!data_registry::defaults_set()) {
return false;
}
- return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
+ return data_registry::get_effective_contextlevel_value($contextlevel, 'category');
}
/**
* @return purpose|false
*/
public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
- self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
}
- /**
- * Creates an expired context record for the provided context id.
- *
- * @param int $contextid
- * @return \tool_dataprivacy\expired_context
- */
- public static function create_expired_context($contextid) {
- self::check_can_manage_data_registry();
-
- $record = (object)[
- 'contextid' => $contextid,
- 'status' => expired_context::STATUS_EXPIRED,
- ];
- $expiredctx = new expired_context(0, $record);
- $expiredctx->save();
-
- return $expiredctx;
- }
-
- /**
- * Deletes an expired context record.
- *
- * @param int $id The tool_dataprivacy_ctxexpire id.
- * @return bool True on success.
- */
- public static function delete_expired_context($id) {
- self::check_can_manage_data_registry();
-
- $expiredcontext = new expired_context($id);
- return $expiredcontext->delete();
- }
-
/**
* Updates the status of an expired context.
*
* @return null
*/
public static function set_expired_context_status(expired_context $expiredctx, $status) {
- self::check_can_manage_data_registry();
-
$expiredctx->set('status', $status);
$expiredctx->save();
}
*/
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
$request = new data_request($requestid);
+ $user = \core_user::get_user($request->get('userid'));
foreach ($clcollection as $contextlist) {
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
foreach ($contextlist->get_contextids() as $contextid) {
if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
$context = \context::instance_by_id($contextid);
- if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
+ $purpose = static::get_effective_context_purpose($context);
+
+ // Data can only be deleted from it if the context is either expired, or unprotected.
+ if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $user)) {
continue;
}
}
+
$context = new contextlist_context();
$context->set('contextid', $contextid)
->set('contextlistid', $contextlistid)
$contexts = [];
}
+ if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
+ $context = \context::instance_by_id($record->contextid);
+ $purpose = static::get_effective_context_purpose($context);
+ // Data can only be deleted from it if the context is either expired, or unprotected.
+ if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $foruser)) {
+ continue;
+ }
+ }
+
$contexts[] = $record->contextid;
$lastcomponent = $record->component;
}
public static function set_context_defaults($contextlevel, $categoryid, $purposeid, $activity = null, $override = false) {
global $DB;
- self::check_can_manage_data_registry();
-
// Get the class name associated with this context level.
$classname = context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname, $activity);
return true;
}
+
+ /**
+ * Format the supplied date interval as a retention period.
+ *
+ * @param \DateInterval $interval
+ * @return string
+ */
+ public static function format_retention_period(\DateInterval $interval) : string {
+ // It is one or another.
+ if ($interval->y) {
+ $formattedtime = get_string('numyears', 'moodle', $interval->format('%y'));
+ } else if ($interval->m) {
+ $formattedtime = get_string('nummonths', 'moodle', $interval->format('%m'));
+ } else if ($interval->d) {
+ $formattedtime = get_string('numdays', 'moodle', $interval->format('%d'));
+ } else {
+ $formattedtime = get_string('retentionperiodzero', 'tool_dataprivacy');
+ }
+
+ return $formattedtime;
+ }
}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry {
-
- /**
- * @var array Inheritance between context levels.
- */
- private static $contextlevelinheritance = [
- CONTEXT_USER => [CONTEXT_SYSTEM],
- CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
- CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
- CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
- CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
- ];
-
/**
* Returns purpose and category var names from a context class name
*
* @return int[]|false[]
*/
public static function get_defaults($contextlevel, $pluginname = '') {
-
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
}
if (empty($purposeid)) {
- $purposeid = false;
+ $purposeid = context_instance::NOTSET;
}
if (empty($categoryid)) {
- $categoryid = false;
+ $categoryid = context_instance::NOTSET;
}
return [$purposeid, $categoryid];
* @param int|false $forcedvalue Use this value as if this was this context instance value.
* @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
*/
- public static function get_effective_context_value(\context $context, $element, $forcedvalue=false) {
+ public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
+ global $DB;
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
$fieldname = $element . 'id';
- if ($forcedvalue === false) {
- $instance = context_instance::get_record_by_contextid($context->id, false);
+ if (!empty($forcedvalue) && ($forcedvalue === context_instance::INHERIT)) {
+ // Do not include the current context when calculating the value.
+ // This has the effect that an inheritted value is calculated.
+ $parentcontextids = $context->get_parent_context_ids(false);
+ } else if (!empty($forcedvalue) && ($forcedvalue !== context_instance::NOTSET)) {
+ return self::get_element_instance($element, $forcedvalue);
+ } else {
+ // Fetch all parent contexts, including self.
+ $parentcontextids = $context->get_parent_context_ids(true);
+ }
+ list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
+ $inparams['contextmodule'] = CONTEXT_MODULE;
- if (!$instance) {
- // If the instance does not have a value defaults to not set, so we grab the context level default as its value.
- $instancevalue = context_instance::NOTSET;
- } else {
- $instancevalue = $instance->get($fieldname);
- }
+ if ('purpose' === $element) {
+ $elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
+ $elementfields = purpose::get_sql_fields('ele', 'ele');
} else {
- $instancevalue = $forcedvalue;
+ $elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
+ $elementfields = category::get_sql_fields('ele', 'ele');
+ }
+ $contextfields = \context_helper::get_preload_record_columns_sql('ctx');
+ $fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
+
+ $sql = "SELECT $fields
+ FROM {context} ctx
+ LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
+ LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
+ LEFT JOIN {modules} m ON m.id = cm.module
+ {$elementjoin}
+ WHERE ctx.id {$insql}
+ ORDER BY ctx.path DESC";
+ $contextinstances = $DB->get_records_sql($sql, $inparams);
+
+ // Check whether this context is a user context, or a child of a user context.
+ // All children of a User context share the same context and cannot be set individually.
+ foreach ($contextinstances as $record) {
+ \context_helper::preload_from_record($record);
+ $parent = \context::instance_by_id($record->id, false);
+
+ if ($parent->contextlevel == CONTEXT_USER) {
+ // Use the context level value for the user.
+ return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
+ }
}
- // Not set.
- if ($instancevalue == context_instance::NOTSET) {
+ foreach ($contextinstances as $record) {
+ $parent = \context::instance_by_id($record->id, false);
- // The effective value varies depending on the context level.
- if ($context->contextlevel == CONTEXT_USER) {
- // Use the context level value as we don't allow people to set specific instances values.
- return self::get_effective_contextlevel_value($context->contextlevel, $element);
- } else {
- // Check if we need to pass the plugin name of an activity.
- $forplugin = '';
- if ($context->contextlevel == CONTEXT_MODULE) {
- list($course, $cm) = get_course_and_cm_from_cmid($context->instanceid);
- $forplugin = $cm->modname;
- }
- // Use the default context level value.
- list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
- $context->contextlevel, false, false, $forplugin
- );
- return self::get_element_instance($element, $$fieldname);
+ $checkcontextlevel = false;
+ if (empty($record->eleid)) {
+ $checkcontextlevel = true;
}
- }
- // Specific value for this context instance.
- if ($instancevalue != context_instance::INHERIT) {
- return self::get_element_instance($element, $instancevalue);
- }
+ if (!empty($forcedvalue) && context_instance::NOTSET === $forcedvalue) {
+ $checkcontextlevel = true;
+ }
- // This context is using inherited so let's return the parent effective value.
- $parentcontext = $context->get_parent_context();
- if (!$parentcontext) {
- return false;
+ if ($checkcontextlevel) {
+ // Check for a value at the contextlevel
+ $forplugin = empty($record->modname) ? '' : $record->modname;
+ list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
+ $parent->contextlevel, false, false, $forplugin);
+
+ $instancevalue = $$fieldname;
+
+ if (context_instance::NOTSET !== $instancevalue && context_instance::INHERIT !== $instancevalue) {
+ // There is an actual value. Return it.
+ return self::get_element_instance($element, $instancevalue);
+ }
+ } else {
+ $elementclass = "\\tool_dataprivacy\\{$element}";
+ $instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
+ $instance->validate();
+
+ return $instance;
+ }
}
- // The forced value should not be transmitted to parent contexts.
- return self::get_effective_context_value($parentcontext, $element);
+ throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
*
* @param int $contextlevel
* @param string $element 'category' or 'purpose'
- * @param int $forcedvalue Use this value as if this was this context level purpose.
* @return \tool_dataprivacy\purpose|false
*/
- public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
-
+ public static function get_effective_contextlevel_value($contextlevel, $element) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
'have a purpose or a category.');
}
- if ($forcedvalue === false) {
- $instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
- if (!$instance) {
- // If the context level does not have a value defaults to not set, so we grab the context level default as
- // its value.
- $instancevalue = context_instance::NOTSET;
- } else {
- $instancevalue = $instance->get($fieldname);
- }
- } else {
- $instancevalue = $forcedvalue;
- }
+ list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
- // Not set -> Use the default context level value.
- if ($instancevalue == context_instance::NOTSET) {
- list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
+ // Note: The $$fieldname points to either $purposeid, or $categoryid.
+ if (context_instance::NOTSET !== $$fieldname && context_instance::INHERIT !== $$fieldname) {
+ // There is a specific value set.
return self::get_element_instance($element, $$fieldname);
}
- // Specific value for this context instance.
- if ($instancevalue != context_instance::INHERIT) {
- return self::get_element_instance($element, $instancevalue);
- }
-
- if ($contextlevel == CONTEXT_SYSTEM) {
- throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
- }
-
- // If we reach this point is that we are inheriting so get the parent context level and repeat.
- $parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
-
- // Forced value are intentionally not passed as the force value should only affect the immediate context level.
- return self::get_effective_contextlevel_value($parentcontextlevel, $element);
+ throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
* @param int $contextlevel
* @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
- * @param string $activity The plugin name of the activity.
+ * @param string $component The name of the component to check.
* @return int[]
*/
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
- $forcedcategoryvalue = false, $activity = '') {
-
- list($purposeid, $categoryid) = self::get_defaults($contextlevel, $activity);
+ $forcedcategoryvalue = false, $component = '') {
+ // Get the defaults for this context level.
+ list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
// Honour forced values.
if ($forcedpurposevalue) {
$categoryid = $forcedcategoryvalue;
}
- // Not set == INHERIT for defaults.
- if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
- $purposeid = false;
- }
- if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
- $categoryid = false;
- }
-
- if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
- foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
+ if ($contextlevel == CONTEXT_USER) {
+ // Only user context levels inherit from a parent context level.
+ list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
- list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
- // Not set == INHERIT for defaults.
- if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
- $parentpurposeid = false;
- }
- if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
- $parentcategoryid = false;
- }
-
- if ($purposeid === false && $parentpurposeid) {
- $purposeid = $parentpurposeid;
- }
+ if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
+ $purposeid = $parentpurposeid;
+ }
- if ($categoryid === false && $parentcategoryid) {
- $categoryid = $parentcategoryid;
- }
+ if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
+ $categoryid = $parentcategoryid;
}
}
- // They may still be false, but we return anyway.
return [$purposeid, $categoryid];
}
* @return \core\persistent
*/
private static function get_element_instance($element, $id) {
-
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('No other elements than purpose and category are allowed');
}
* @return array
*/
protected static function define_properties() {
- return array(
- 'contextid' => array(
+ return [
+ 'contextid' => [
'type' => PARAM_INT,
'description' => 'The context id.',
- ),
- 'status' => array(
+ ],
+ 'defaultexpired' => [
+ 'type' => PARAM_INT,
+ 'description' => 'Whether to default retention period for the purpose has been reached',
+ 'default' => 1,
+ ],
+ 'expiredroles' => [
+ 'type' => PARAM_TEXT,
+ 'description' => 'This list of roles to include during deletion',
+ 'default' => '',
+ ],
+ 'unexpiredroles' => [
+ 'type' => PARAM_TEXT,
+ 'description' => 'This list of roles to exclude during deletion',
+ 'default' => '',
+ ],
+ 'status' => [
'choices' => [
self::STATUS_EXPIRED,
self::STATUS_APPROVED,
],
'type' => PARAM_INT,
'description' => 'The deletion status of the context.',
- ),
- );
+ ],
+ ];
}
/**
return $DB->count_records_sql($sql, $params);
}
+
+ /**
+ * Set the list of role IDs for either expiredroles, or unexpiredroles.
+ *
+ * @param string $field
+ * @param int[] $roleids
+ * @return expired_context
+ */
+ protected function set_roleids_for(string $field, array $roleids) : expired_context {
+ $roledata = json_encode($roleids);
+
+ $this->raw_set($field, $roledata);
+
+ return $this;
+ }
+
+ /**
+ * Get the list of role IDs for either expiredroles, or unexpiredroles.
+ *
+ * @param string $field
+ * @return int[]
+ */
+ protected function get_roleids_for(string $field) {
+ $value = $this->raw_get($field);
+ if (empty($value)) {
+ return [];
+ }
+
+ return json_decode($value);
+ }
+
+ /**
+ * Set the list of unexpired role IDs.
+ *
+ * @param int[] $roleids
+ * @return expired_context
+ */
+ protected function set_unexpiredroles(array $roleids) : expired_context {
+ $this->set_roleids_for('unexpiredroles', $roleids);
+
+ return $this;
+ }
+
+ /**
+ * Add a set of role IDs to the list of expired role IDs.
+ *
+ * @param int[] $roleids
+ * @return expired_context
+ */
+ public function add_expiredroles(array $roleids) : expired_context {
+ $existing = $this->get('expiredroles');
+ $newvalue = array_merge($existing, $roleids);
+
+ $this->set('expiredroles', $newvalue);
+
+ return $this;
+ }
+
+ /**
+ * Add a set of role IDs to the list of unexpired role IDs.
+ *
+ * @param int[] $roleids
+ * @return unexpired_context
+ */
+ public function add_unexpiredroles(array $roleids) : expired_context {
+ $existing = $this->get('unexpiredroles');
+ $newvalue = array_merge($existing, $roleids);
+
+ $this->set('unexpiredroles', $newvalue);
+
+ return $this;
+ }
+
+ /**
+ * Set the list of expired role IDs.
+ *
+ * @param int[] $roleids
+ * @return expired_context
+ */
+ protected function set_expiredroles(array $roleids) : expired_context {
+ $this->set_roleids_for('expiredroles', $roleids);
+
+ return $this;
+ }
+
+ /**
+ * Get the list of expired role IDs.
+ *
+ * @return int[]
+ */
+ protected function get_expiredroles() {
+ return $this->get_roleids_for('expiredroles');
+ }
+
+ /**
+ * Get the list of unexpired role IDs.
+ *
+ * @return int[]
+ */
+ protected function get_unexpiredroles() {
+ return $this->get_roleids_for('unexpiredroles');
+ }
+
+ /**
+ * Create a new expired_context based on the context, and expiry_info object.
+ *
+ * @param \context $context
+ * @param expiry_info $info
+ * @param boolean $save
+ * @return expired_context
+ */
+ public static function create_from_expiry_info(\context $context, expiry_info $info, bool $save = true) : expired_context {
+ $record = (object) [
+ 'contextid' => $context->id,
+ 'status' => self::STATUS_EXPIRED,
+ 'defaultexpired' => (int) $info->is_default_expired(),
+ ];
+
+ $expiredcontext = new static(0, $record);
+ $expiredcontext->set('expiredroles', $info->get_expired_roles());
+ $expiredcontext->set('unexpiredroles', $info->get_unexpired_roles());
+
+ if ($save) {
+ $expiredcontext->save();
+ }
+
+ return $expiredcontext;
+ }
+
+ /**
+ * Update the expired_context from an expiry_info object which relates to this context.
+ *
+ * @param expiry_info $info
+ * @return $this
+ */
+ public function update_from_expiry_info(expiry_info $info) : expired_context {
+ $save = false;
+
+ // Compare the expiredroles.
+ $thisexpired = $this->get('expiredroles');
+ $infoexpired = $info->get_expired_roles();
+
+ sort($thisexpired);
+ sort($infoexpired);
+ if ($infoexpired != $thisexpired) {
+ $this->set('expiredroles', $infoexpired);
+ $save = true;
+ }
+
+ // Compare the unexpiredroles.
+ $thisunexpired = $this->get('unexpiredroles');
+ $infounexpired = $info->get_unexpired_roles();
+
+ sort($thisunexpired);
+ sort($infounexpired);
+ if ($infounexpired != $thisunexpired) {
+ $this->set('unexpiredroles', $infounexpired);
+ $save = true;
+ }
+
+ if (empty($this->get('defaultexpired')) == $info->is_default_expired()) {
+ $this->set('defaultexpired', (int) $info->is_default_expired());
+ $save = true;
+ }
+
+ if ($save) {
+ $this->set('status', self::STATUS_EXPIRED);
+ $this->save();
+ }
+
+ return $this;
+
+ }
+
+ /**
+ * Check whether this expired_context record is in a state ready for deletion to actually take place.
+ *
+ * @return bool
+ */
+ public function can_process_deletion() : bool {
+ return ($this->get('status') == self::STATUS_APPROVED);
+ }
+
+ /**
+ * Check whether this expired_context record has already been cleaned.
+ *
+ * @return bool
+ */
+ public function is_complete() : bool {
+ return ($this->get('status') == self::STATUS_CLEANED);
+ }
+
+ /**
+ * Whether this context has 'fully' expired.
+ * That is to say that the default retention period has been reached, and that there are no unexpired roles.
+ *
+ * @return bool
+ */
+ public function is_fully_expired() : bool {
+ return $this->get('defaultexpired') && empty($this->get('unexpiredroles'));
+ }
}
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-abstract class expired_contexts_manager {
+class expired_contexts_manager {
/**
* Number of deleted contexts for each scheduled task run.
*/
const DELETE_LIMIT = 200;
+ /** @var progress_trace The log progress tracer */
+ protected $progresstracer = null;
+
+ /** @var manager The privacy manager */
+ protected $manager = null;
+
+ /** @var \progress_trace Trace tool for logging */
+ protected $trace = null;
+
/**
- * Returns the list of expired context instances.
+ * Constructor for the expired_contexts_manager.
*
- * @return \stdClass[]
+ * @param \progress_trace $trace
*/
- abstract protected function get_expired_contexts();
+ public function __construct(\progress_trace $trace = null) {
+ if (null === $trace) {
+ $trace = new \null_progress_trace();
+ }
+
+ $this->trace = $trace;
+ }
/**
- * Specify with context levels this expired contexts manager is deleting.
+ * Flag expired contexts as expired.
*
- * @return int[]
+ * @return int[] The number of contexts flagged as expired for courses, and users.
*/
- abstract protected function get_context_levels();
+ public function flag_expired_contexts() : array {
+ $this->trace->output('Checking requirements');
+ if (!$this->check_requirements()) {
+ $this->trace->output('Requirements not met. Cannot process expired retentions.', 1);
+ return [0, 0];
+ }
+
+ // Clear old and stale records first.
+ $this->trace->output('Clearing obselete records.', 0);
+ static::clear_old_records();
+ $this->trace->output('Done.', 1);
+
+ $this->trace->output('Calculating potential course expiries.', 0);
+ $data = static::get_nested_expiry_info_for_courses();
+
+ $coursecount = 0;
+ $this->trace->output('Updating course expiry data.', 0);
+ foreach ($data as $expiryrecord) {
+ if ($this->update_from_expiry_info($expiryrecord)) {
+ $coursecount++;
+ }
+ }
+ $this->trace->output('Done.', 1);
+
+ $this->trace->output('Calculating potential user expiries.', 0);
+ $data = static::get_nested_expiry_info_for_user();
+
+ $usercount = 0;
+ $this->trace->output('Updating user expiry data.', 0);
+ foreach ($data as $expiryrecord) {
+ if ($this->update_from_expiry_info($expiryrecord)) {
+ $usercount++;
+ }
+ }
+ $this->trace->output('Done.', 1);
+
+ return [$coursecount, $usercount];
+ }
/**
- * Flag expired contexts as expired.
- *
- * @return int The number of contexts flagged as expired.
+ * Clear old and stale records.
*/
- public function flag_expired() {
+ protected static function clear_old_records() {
+ global $DB;
- if (!$this->check_requirements()) {
- return 0;
+ $sql = "SELECT dpctx.*
+ FROM {tool_dataprivacy_ctxexpired} dpctx
+ LEFT JOIN {context} ctx ON ctx.id = dpctx.contextid
+ WHERE ctx.id IS NULL";
+
+ $orphaned = $DB->get_recordset_sql($sql);
+ foreach ($orphaned as $orphan) {
+ $expiredcontext = new expired_context(0, $orphan);
+ $expiredcontext->delete();
}
- $contexts = $this->get_expired_contexts();
- foreach ($contexts as $context) {
- api::create_expired_context($context->id);
+ // Delete any child of a user context.
+ $parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
+ $params = [
+ 'contextuser' => CONTEXT_USER,
+ ];
+
+ $sql = "SELECT dpctx.*
+ FROM {tool_dataprivacy_ctxexpired} dpctx
+ WHERE dpctx.contextid IN (
+ SELECT ctx.id
+ FROM {context} ctxuser
+ JOIN {context} ctx ON ctx.path LIKE {$parentpath}
+ WHERE ctxuser.contextlevel = :contextuser
+ )";
+ $userchildren = $DB->get_recordset_sql($sql, $params);
+ foreach ($userchildren as $child) {
+ $expiredcontext = new expired_context(0, $child);
+ $expiredcontext->delete();
}
+ }
- return count($contexts);
+ /**
+ * Get the full nested set of expiry data relating to all contexts.
+ *
+ * @param string $contextpath A contexpath to restrict results to
+ * @return \stdClass[]
+ */
+ protected static function get_nested_expiry_info($contextpath = '') : array {
+ $coursepaths = self::get_nested_expiry_info_for_courses($contextpath);
+ $userpaths = self::get_nested_expiry_info_for_user($contextpath);
+
+ return array_merge($coursepaths, $userpaths);
}
/**
- * Deletes the expired contexts.
+ * Get the full nested set of expiry data relating to course-related contexts.
*
- * @return int The number of deleted contexts.
+ * @param string $contextpath A contexpath to restrict results to
+ * @return \stdClass[]
*/
- public function delete() {
+ protected static function get_nested_expiry_info_for_courses($contextpath = '') : array {
+ global $DB;
- $numprocessed = 0;
+ $contextfields = \context_helper::get_preload_record_columns_sql('ctx');
+ $expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
+ $purposefields = 'dpctx.purposeid';
+ $coursefields = 'ctxcourse.expirydate AS expirydate';
+ $fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $coursefields, $purposefields]);
- if (!$this->check_requirements()) {
- return $numprocessed;
+ // We want all contexts at course-dependa