. /** * Expired contexts manager for CONTEXT_USER. * * @package tool_dataprivacy * @copyright 2018 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace tool_dataprivacy; use core_privacy\manager; defined('MOODLE_INTERNAL') || die(); /** * Expired contexts manager for CONTEXT_USER. * * @copyright 2018 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager { /** * Only user level. * * @return int[] */ protected function get_context_levels() { return [CONTEXT_USER]; } /** * Returns the user context instances that are expired. * * @return \stdClass[] */ protected function get_expired_contexts() { global $DB; // Including context info + last login timestamp. $fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx'); $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER); // Calculate what is considered expired according to the context level effective purpose (= now + retention period). $expiredtime = new \DateTime(); $retention = new \DateInterval($purpose->get('retentionperiod')); $expiredtime->sub($retention); $sql = "SELECT $fields FROM {context} ctx JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL ORDER BY ctx.path, ctx.contextlevel ASC"; $possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]); $expiredcontexts = []; foreach ($possiblyexpired as $record) { \context_helper::preload_from_record($record); // No strict checking as the context may already be deleted (e.g. we just deleted a course, // module contexts below it will not exist). $context = \context::instance_by_id($record->id, false); if (!$context) { continue; } if (is_siteadmin($context->instanceid)) { continue; } $courses = enrol_get_users_courses($context->instanceid, false, ['enddate']); foreach ($courses as $course) { if (!$course->enddate) { // We can not know it what is going on here, so we prefer to be conservative. continue 2; } if ($course->enddate >= time()) { // Future or ongoing course. continue 2; } } $expiredcontexts[$context->id] = $context; } return $expiredcontexts; } /** * Deletes user data from the provided context. * * Overwritten to delete the user. * * @param manager $privacymanager * @param expired_context $expiredctx * @return \context|false */ protected function delete_expired_context(manager $privacymanager, expired_context $expiredctx) { $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING); if (!$context) { api::delete_expired_context($expiredctx->get('contextid')); return false; } if (!PHPUNIT_TEST) { mtrace('Deleting context ' . $context->id . ' - ' . shorten_text($context->get_context_name(true, true))); } // To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray // contexts for the user that may still exist and call delete_data_for_user(). $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST); $approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id); $contextlistcollection = $privacymanager->get_contexts_for_userid($user->id); foreach ($contextlistcollection as $contextlist) { $approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist( $user, $contextlist->get_component(), $contextlist->get_contextids() )); } $privacymanager->delete_data_for_user($approvedlistcollection); api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED); // Delete the user. delete_user($user); return $context; } }