}));
if (!$shouldskip && $context instanceof \context_user) {
- // The context instanceid is the user's ID.
- if (isguestuser($context->instanceid) || is_siteadmin($context->instanceid)) {
- // This is an admin, or the guest and cannot be deleted.
- $shouldskip = true;
- }
-
- if (!$shouldskip) {
- $courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
- $requireenddate = self::require_all_end_dates_for_user_deletion();
-
- foreach ($courses as $course) {
- if (empty($course->enddate)) {
- // This course has no end date.
- if ($requireenddate) {
- // Course end dates are required, and this course has no end date.
- $shouldskip = true;
- break;
- }
-
- // Course end dates are not required. The subsequent checks are pointless at this time so just
- // skip them.
- continue;
- }
-
- if ($course->enddate >= time()) {
- // This course is still in the future.
- $shouldskip = true;
- break;
- }
-
- // This course has an end date which is in the past.
- if (!self::is_course_expired($course)) {
- // This course has not expired yet.
- $shouldskip = true;
- break;
- }
- }
- }
+ $shouldskip = !self::are_user_context_dependencies_expired($context);
}
if ($shouldskip) {
$defaultexpired = static::has_expired($purpose->get('retentionperiod'), $comparisondate);
}
- return new expiry_info($defaultexpired, [], []);
+ return new expiry_info($defaultexpired, $purpose->get('protected'), [], [], []);
} else {
+ $protectedroles = [];
foreach ($overrides as $override) {
if (static::has_expired($override->get('retentionperiod'), $comparisondate)) {
// This role has expired.
} else {
// This role has not yet expired.
$unexpiredroles[] = $override->get('roleid');
+
+ if ($override->get('protected')) {
+ $protectedroles[$override->get('roleid')] = true;
+ }
}
}
$expiredroles = [];
}
- return new expiry_info($defaultexpired, $expiredroles, $unexpiredroles);
+ return new expiry_info($defaultexpired, $purpose->get('protected'), $expiredroles, $unexpiredroles, $protectedroles);
}
}
// Find the list of expired role users.
$expiredroleuserassignments = get_role_users($expiredroles, $context, true, 'ra.id, u.id AS userid', 'ra.id');
$expiredroleusers = array_map(function($assignment) {
- return $assignment->userid;
- }, $expiredroleuserassignments);
+ return $assignment->userid;
+ }, $expiredroleuserassignments);
}
$expiredroleusers = array_unique($expiredroleusers);
// Find the list of unexpired role users.
$unexpiredroleuserassignments = get_role_users($unexpiredroles, $context, true, 'ra.id, u.id AS userid', 'ra.id');
$unexpiredroleusers = array_map(function($assignment) {
- return $assignment->userid;
- }, $unexpiredroleuserassignments);
+ return $assignment->userid;
+ }, $unexpiredroleuserassignments);
}
$unexpiredroleusers = array_unique($unexpiredroleusers);
$tofilter = array_filter($tofilter, function($userroles) use ($expiredroles) {
// Each iteration contains the list of role assignment for a specific user.
// All roles that the user holds must match those in the list of expired roles.
- if (count($userroles) === 1) {
- // Shortcut - only one role held which must be one of the expired roles.
- // TODO I think this is wrong.
- return false;
- }
-
foreach ($userroles as $ra) {
if (false === array_search($ra->roleid, $expiredroles)) {
// This role was not found in the list of assignments.
];
}
+ /**
+ * Determine whether the supplied context has expired.
+ *
+ * @param \context $context
+ * @return bool
+ */
+ public static function is_context_expired(\context $context) : bool {
+ $parents = $context->get_parent_contexts(true);
+ foreach ($parents as $parent) {
+ if ($parent instanceof \context_course) {
+ return self::is_course_context_expired($context);
+ }
+
+ if ($parent instanceof \context_user) {
+ return self::are_user_context_dependencies_expired($context);
+ }
+ }
+
+ return false;
+ }
+
/**
* Check whether the course has expired.
*
*/
protected static function is_course_expired(\stdClass $course) : bool {
$context = \context_course::instance($course->id);
+
+ return self::is_course_context_expired($context);
+ }
+
+ /**
+ * Determine whether the supplied course context has expired.
+ *
+ * @param \context_course $context
+ * @return bool
+ */
+ protected static function is_course_context_expired(\context_course $context) : bool {
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
return !empty($expiryrecords[$context->path]) && $expiryrecords[$context->path]->info->is_fully_expired();
}
+ /**
+ * Determine whether the supplied user context's dependencies have expired.
+ *
+ * This checks whether courses have expired, and some other check, but does not check whether the user themself has expired.
+ *
+ * Although this seems unusual at first, each location calling this actually checks whether the user is elgible for
+ * deletion, irrespective if they have actually expired.
+ *
+ * For example, a request to delete the user only cares about course dependencies and the user's lack of expiry
+ * should not block their own request to be deleted; whilst the expiry eligibility check has already tested for the
+ * user being expired.
+ *
+ * @param \context_user $context
+ * @return bool
+ */
+ protected static function are_user_context_dependencies_expired(\context_user $context) : bool {
+ // The context instanceid is the user's ID.
+ if (isguestuser($context->instanceid) || is_siteadmin($context->instanceid)) {
+ // This is an admin, or the guest and cannot expire.
+ return false;
+ }
+
+ $courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
+ $requireenddate = self::require_all_end_dates_for_user_deletion();
+
+ $expired = true;
+
+ foreach ($courses as $course) {
+ if (empty($course->enddate)) {
+ // This course has no end date.
+ if ($requireenddate) {
+ // Course end dates are required, and this course has no end date.
+ $expired = false;
+ break;
+ }
+
+ // Course end dates are not required. The subsequent checks are pointless at this time so just
+ // skip them.
+ continue;
+ }
+
+ if ($course->enddate >= time()) {
+ // This course is still in the future.
+ $expired = false;
+ break;
+ }
+
+ // This course has an end date which is in the past.
+ if (!self::is_course_expired($course)) {
+ // This course has not expired yet.
+ $expired = false;
+ break;
+ }
+ }
+
+ return $expired;
+ }
+
+ /**
+ * Determine whether the supplied context has expired or unprotected for the specified user.
+ *
+ * @param \context $context
+ * @param \stdClass $user
+ * @return bool
+ */
+ public static function is_context_expired_or_unprotected_for_user(\context $context, \stdClass $user) : bool {
+ $parents = $context->get_parent_contexts(true);
+ foreach ($parents as $parent) {
+ if ($parent instanceof \context_course) {
+ return self::is_course_context_expired_or_unprotected_for_user($parent, $user);
+ }
+
+ if ($parent instanceof \context_user) {
+ return self::are_user_context_dependencies_expired($context);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine whether the supplied course context has expired, or is unprotected.
+ *
+ * @param \context_course $context
+ * @param \stdClass $user
+ * @return bool
+ */
+ protected static function is_course_context_expired_or_unprotected_for_user(\context_course $context, \stdClass $user) {
+ $expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
+
+ $info = $expiryrecords[$context->path]->info;
+ if ($info->is_fully_expired()) {
+ // This context is fully expired.
+ return true;
+ }
+
+ // Now perform user checks.
+ $userroles = array_map(function($assignment) {
+ return $assignment->roleid;
+ }, get_user_roles($context, $user->id));
+
+ $unexpiredprotectedroles = $info->get_unexpired_protected_roles();
+ if (!empty(array_intersect($unexpiredprotectedroles, $userroles))) {
+ // The user holds an unexpired and protected role.
+ return false;
+ }
+
+ $unprotectedoverriddenroles = $info->get_unprotected_overridden_roles();
+ $matchingroles = array_intersect($unprotectedoverriddenroles, $userroles);
+ if (!empty($matchingroles)) {
+ // This user has at least one overridden role which is not a protected.
+ // However, All such roles must match.
+ // If the user has multiple roles then all must be expired, otherwise we should fall back to the default behaviour.
+ if (empty(array_diff($userroles, $unprotectedoverriddenroles))) {
+ // All roles that this user holds are a combination of expired, or unprotected.
+ return true;
+ }
+ }
+
+ if ($info->is_default_expired()) {
+ // If the user has no unexpired roles, and the context is expired by default then this must be expired.
+ return true;
+ }
+
+ return !$info->is_default_protected();
+ }
+
/**
* Create a new instance of the privacy manager.
*
use core\invalid_persistent_exception;
use core\task\manager;
+use tool_dataprivacy\contextlist_context;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\expired_context;
use tool_dataprivacy\data_request;
+use tool_dataprivacy\purpose;
+use tool_dataprivacy\category;
use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
$this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
}
+ /**
+ * Test that delete requests do not filter out protected purpose contexts if they are already expired.
+ */
+ public function test_add_request_contexts_with_status_delete_course_expired_protected() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $collection = new \core_privacy\local\request\contextlist_collection($user->id);
+ $contextlist = new \core_privacy\local\request\contextlist();
+ $contextlist->set_component('tool_dataprivacy');
+ $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
+ $collection->add_contextlist($contextlist);
+
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+
+ $purposes->course->set('protected', 1)->save();
+ api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
+
+ $requests = contextlist_context::get_records();
+ $this->assertCount(1, $requests);
+ }
+
+ /**
+ * Test that delete requests does filter out protected purpose contexts which are not expired.
+ */
+ public function test_add_request_contexts_with_status_delete_course_unexpired_protected() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $collection = new \core_privacy\local\request\contextlist_collection($user->id);
+ $contextlist = new \core_privacy\local\request\contextlist();
+ $contextlist->set_component('tool_dataprivacy');
+ $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
+ $collection->add_contextlist($contextlist);
+
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+
+ $purposes->course->set('protected', 1)->save();
+ api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
+
+ $requests = contextlist_context::get_records();
+ $this->assertCount(0, $requests);
+ }
+
+ /**
+ * Test that delete requests do not filter out unexpired contexts if they are not protected.
+ */
+ public function test_add_request_contexts_with_status_delete_course_unexpired_unprotected() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $collection = new \core_privacy\local\request\contextlist_collection($user->id);
+ $contextlist = new \core_privacy\local\request\contextlist();
+ $contextlist->set_component('tool_dataprivacy');
+ $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx1)', ['ctx1' => $coursecontext->id]);
+ $collection->add_contextlist($contextlist);
+
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+
+ $purposes->course->set('protected', 0)->save();
+ api::add_request_contexts_with_status($collection, $request->get('id'), contextlist_context::STATUS_APPROVED);
+
+ $requests = contextlist_context::get_records();
+ $this->assertCount(1, $requests);
+ }
+
+ /**
+ * Test that delete requests do not filter out protected purpose contexts if they are already expired.
+ */
+ public function test_get_approved_contextlist_collection_for_request_delete_course_expired_protected() {
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ // Create the request, with its contextlist and context.
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+ $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
+ $contextlist->save();
+
+ $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
+ 'contextid' => $coursecontext->id,
+ 'status' => contextlist_context::STATUS_APPROVED,
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $clcontext->save();
+
+ $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
+ 'requestid' => $request->get('id'),
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $rcl->save();
+
+ $purposes->course->set('protected', 1)->save();
+ $collection = api::get_approved_contextlist_collection_for_request($request);
+
+ $this->assertCount(1, $collection);
+
+ $list = $collection->get_contextlist_for_component('tool_dataprivacy');
+ $this->assertCount(1, $list);
+ }
+
+ /**
+ * Test that delete requests does filter out protected purpose contexts which are not expired.
+ */
+ public function test_get_approved_contextlist_collection_for_request_delete_course_unexpired_protected() {
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ // Create the request, with its contextlist and context.
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+ $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
+ $contextlist->save();
+
+ $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
+ 'contextid' => $coursecontext->id,
+ 'status' => contextlist_context::STATUS_APPROVED,
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $clcontext->save();
+
+ $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
+ 'requestid' => $request->get('id'),
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $rcl->save();
+
+ $purposes->course->set('protected', 1)->save();
+ $collection = api::get_approved_contextlist_collection_for_request($request);
+
+ $this->assertCount(0, $collection);
+
+ $list = $collection->get_contextlist_for_component('tool_dataprivacy');
+ $this->assertEmpty($list);
+ }
+
+ /**
+ * Test that delete requests do not filter out unexpired contexts if they are not protected.
+ */
+ public function test_get_approved_contextlist_collection_for_request_delete_course_unexpired_unprotected() {
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
+ $purposes->course->set('protected', 1)->save();
+
+ $user = $this->getDataGenerator()->create_user();
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ // Create the request, with its contextlist and context.
+ $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
+ $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']);
+ $contextlist->save();
+
+ $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [
+ 'contextid' => $coursecontext->id,
+ 'status' => contextlist_context::STATUS_APPROVED,
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $clcontext->save();
+
+ $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [
+ 'requestid' => $request->get('id'),
+ 'contextlistid' => $contextlist->get('id'),
+ ]);
+ $rcl->save();
+
+ $purposes->course->set('protected', 0)->save();
+ $collection = api::get_approved_contextlist_collection_for_request($request);
+
+ $this->assertCount(1, $collection);
+
+ $list = $collection->get_contextlist_for_component('tool_dataprivacy');
+ $this->assertCount(1, $list);
+ }
+
/**
* Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
*/
'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
];
}
+
+ /**
+ * Setup the basics with the specified retention period.
+ *
+ * @param string $system Retention policy for the system.
+ * @param string $user Retention policy for users.
+ * @param string $course Retention policy for courses.
+ * @param string $activity Retention policy for activities.
+ */
+ protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
+ $this->resetAfterTest();
+
+ $purposes = (object) [
+ 'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
+ 'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
+ ];
+
+ if (null !== $course) {
+ $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
+ }
+
+ if (null !== $activity) {
+ $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
+ }
+
+ return $purposes;
+ }
+
+ /**
+ * Create a retention period and set it for the specified context level.
+ *
+ * @param string $retention
+ * @param int $contextlevel
+ * @return purpose
+ */
+ protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) : purpose {
+ $purpose = new purpose(0, (object) [
+ 'name' => 'Test purpose ' . rand(1, 1000),
+ 'retentionperiod' => $retention,
+ 'lawfulbases' => 'gdpr_art_6_1_a',
+ ]);
+ $purpose->create();
+
+ $cat = new category(0, (object) ['name' => 'Test category']);
+ $cat->create();
+
+ if ($contextlevel <= CONTEXT_USER) {
+ $record = (object) [
+ 'purposeid' => $purpose->get('id'),
+ 'categoryid' => $cat->get('id'),
+ 'contextlevel' => $contextlevel,
+ ];
+ api::set_contextlevel($record);
+ } else {
+ list($purposevar, ) = data_registry::var_names_from_context(
+ \context_helper::get_class_for_level(CONTEXT_COURSE)
+ );
+ set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
+ }
+
+ return $purpose;
+ }
}
use tool_dataprivacy\purpose_override;
use tool_dataprivacy\category;
use tool_dataprivacy\contextlevel;
+use tool_dataprivacy\expired_contexts_manager;
defined('MOODLE_INTERNAL') || die();
global $CFG;
* @param string $course Retention policy for courses.
* @param string $activity Retention policy for activities.
*/
- protected function setup_basics(string $system, string $user, string $course, string $activity = null) : array {
+ protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
$this->resetAfterTest();
- $purposes = [];
- $purposes[] = $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM);
- $purposes[] = $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER);
- $purposes[] = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
+ $purposes = (object) [
+ 'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
+ 'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
+ ];
+
+ if (null !== $course) {
+ $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
+ }
+
if (null !== $activity) {
- $purposes[] = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
+ $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
}
return $purposes;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $userpurpose = $purposes[1];
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$role = $DB->get_record('role', ['shortname' => 'manager']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $userpurpose->get('id'),
+ 'purposeid' => $purposes->user->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
public function test_flag_user_retention_changed() {
$this->resetAfterTest();
- list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$this->assertNotFalse($expiredcontext);
// Increase the retention period to 5 years.
- $userpurpose->set('retentionperiod', 'P5Y');
- $userpurpose->save();
+ $purposes->user->set('retentionperiod', 'P5Y');
+ $purposes->user->save();
// Re-run the expiry job - the previously flagged user will be removed because the retention period has been increased.
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
public function test_flag_user_historic_block_unapproved() {
$this->resetAfterTest();
- list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
public function test_flag_user_historic_unexpired_child() {
$this->resetAfterTest();
- list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
+ $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'student']);
// The role has a much shorter retention, but both should match.
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('P1Y', 'P1Y', 'P1Y');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
(new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1S',
]))->save();
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $userpurpose = $purposes[1];
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$role = $DB->get_record('role', ['shortname' => 'manager']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $userpurpose->get('id'),
+ 'purposeid' => $purposes->user->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
- $coursepurpose = $purposes[2];
$role = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
- $coursepurpose = $purposes[2];
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $studentrole->id,
'retentionperiod' => 'PT1M',
]);
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
- 'purposeid' => $coursepurpose->get('id'),
+ 'purposeid' => $purposes->course->get('id'),
'roleid' => $teacherrole->id,
'retentionperiod' => 'PT1M',
]);
public function test_process_user_historic_block_unapproved() {
$this->resetAfterTest();
- list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
public function test_process_user_historic_unexpired_child() {
$this->resetAfterTest();
- list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
- $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
+ $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+ $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
- $coursepurpose = $purposes[2];
- $coursepurpose->set('retentionperiod', 'P5Y');
- $coursepurpose->save();
+ $purposes->course->set('retentionperiod', 'P5Y');
+ $purposes->course->save();
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
return $block;
}
+
+ /**
+ * Test the is_context_expired functions when supplied with the system context.
+ */
+ public function test_is_context_expired_system() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setup_basics('PT1H', 'PT1H', 'P1D');
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+
+ $this->assertFalse(expired_contexts_manager::is_context_expired(\context_system::instance()));
+ $this->assertFalse(
+ expired_contexts_manager::is_context_expired_or_unprotected_for_user(\context_system::instance(), $user));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an expired course.
+ */
+ public function test_is_context_expired_course_expired() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+
+ $purposes->course->set('protected', 1)->save();
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->course->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an unexpired course.
+ */
+ public function test_is_context_expired_course_unexpired() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $this->assertTrue(expired_contexts_manager::is_context_expired($coursecontext));
+
+ $purposes->course->set('protected', 1)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->course->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an expired course which has role overrides.
+ */
+ public function test_is_context_expired_course_expired_override() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'manager']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->course->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'P5Y',
+ ]);
+ $override->save();
+ role_assign($role->id, $user->id, $systemcontext->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+
+ $purposes->course->set('protected', 1)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->course->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an expired course which has role overrides.
+ */
+ public function test_is_context_expired_course_expired_override_parent() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'manager']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->system->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'P5Y',
+ ]);
+ $override->save();
+ role_assign($role->id, $user->id, $systemcontext->id);
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+
+ // The user override applies to this user. THIs means that the default expiry has no effect.
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->system->set('protected', 1)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->system->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $override->set('protected', 1)->save();
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->system->set('protected', 1)->save();
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ $purposes->system->set('protected', 0)->save();
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
+
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an expired course which has role overrides but the user
+ * does not hold the role.
+ */
+ public function test_is_context_expired_course_expired_override_parent_no_role() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1H', 'PT1H');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'manager']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->system->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'P5Y',
+ ]);
+ $override->save();
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ // This context is not _fully _ expired.
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
+ */
+ public function test_is_context_expired_course_expired_override_inverse() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('P1Y', 'P1Y');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'student']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->system->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'PT1S',
+ ]);
+ $override->save();
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ // This context is not _fully _ expired.
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
+ */
+ public function test_is_context_expired_course_expired_override_inverse_parent() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('P1Y', 'P1Y');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'manager']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->system->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'PT1S',
+ ]);
+ $override->save();
+
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+ role_assign($role->id, $user->id, $systemcontext->id);
+
+ $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+ role_unassign($studentrole->id, $user->id, $coursecontext->id);
+
+ // This context is not _fully _ expired.
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+ }
+
+ /**
+ * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
+ */
+ public function test_is_context_expired_course_expired_override_inverse_parent_not_assigned() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('P1Y', 'P1Y');
+
+ $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $role = $DB->get_record('role', ['shortname' => 'manager']);
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->system->get('id'),
+ 'roleid' => $role->id,
+ 'retentionperiod' => 'PT1S',
+ ]);
+ $override->save();
+
+ // Enrol the user in the course without any role.
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+ $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+ role_unassign($studentrole->id, $user->id, $coursecontext->id);
+
+ // This context is not _fully _ expired.
+ $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
+ }
+
+ /**
+ * Ensure that context expired checks for a specific user taken into account roles.
+ */
+ public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('PT1S', 'PT1S', 'PT1S');
+
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id');
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->course->get('id'),
+ 'roleid' => $roles['manager'],
+ 'retentionperiod' => 'P1W',
+ 'protected' => 1,
+ ]);
+ $override->save();
+
+ $s = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student');
+
+ $t = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
+
+ $sm = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student');
+ role_assign($roles['manager'], $sm->id, $coursecontext->id);
+
+ $m = $this->getDataGenerator()->create_user();
+ role_assign($roles['manager'], $m->id, $coursecontext->id);
+
+ $tm = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
+ role_assign($roles['manager'], $tm->id, $coursecontext->id);
+
+ // The context should only be expired for users who are not a manager.
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
+
+ $override->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
+ }
+
+ /**
+ * Ensure that context expired checks for a specific user taken into account roles when retention is inversed.
+ */
+ public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected_inverse() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ $purposes = $this->setup_basics('P5Y', 'P5Y', 'P5Y');
+
+ $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]);
+ $coursecontext = \context_course::instance($course->id);
+ $systemcontext = \context_system::instance();
+
+ $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id');
+ $override = new purpose_override(0, (object) [
+ 'purposeid' => $purposes->course->get('id'),
+ 'roleid' => $roles['student'],
+ 'retentionperiod' => 'PT1S',
+ ]);
+ $override->save();
+
+ $s = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student');
+
+ $t = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
+
+ $sm = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student');
+ role_assign($roles['manager'], $sm->id, $coursecontext->id);
+
+ $m = $this->getDataGenerator()->create_user();
+ role_assign($roles['manager'], $m->id, $coursecontext->id);
+
+ $tm = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
+ role_assign($roles['manager'], $tm->id, $coursecontext->id);
+
+ // The context should only be expired for users who are only a student.
+ $purposes->course->set('protected', 1)->save();
+ $override->set('protected', 1)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
+ $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
+
+ $purposes->course->set('protected', 0)->save();
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
+ $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
+ }
}