MDL-63496 tool_dataprivacy: Respect expiry with protected flag

This issue is a part of the MDL-62560 Epic.
This commit is contained in:
Andrew Nicols
2018-10-17 13:13:54 +08:00
parent 037f741214
commit f3e39f4bce
5 changed files with 928 additions and 89 deletions
+15 -1
View File
@@ -988,6 +988,7 @@ class api {
*/
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);
@@ -998,10 +999,14 @@ class api {
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)
@@ -1099,6 +1104,15 @@ class api {
$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;
}
@@ -311,44 +311,7 @@ class expired_contexts_manager {
}));
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) {
@@ -586,8 +549,9 @@ class expired_contexts_manager {
$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.
@@ -595,6 +559,10 @@ class expired_contexts_manager {
} else {
// This role has not yet expired.
$unexpiredroles[] = $override->get('roleid');
if ($override->get('protected')) {
$protectedroles[$override->get('roleid')] = true;
}
}
}
@@ -607,7 +575,7 @@ class expired_contexts_manager {
$expiredroles = [];
}
return new expiry_info($defaultexpired, $expiredroles, $unexpiredroles);
return new expiry_info($defaultexpired, $purpose->get('protected'), $expiredroles, $unexpiredroles, $protectedroles);
}
}
@@ -728,8 +696,8 @@ class expired_contexts_manager {
// 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);
@@ -739,8 +707,8 @@ class expired_contexts_manager {
// 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);
@@ -749,12 +717,6 @@ class expired_contexts_manager {
$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.
@@ -773,6 +735,27 @@ class expired_contexts_manager {
];
}
/**
* 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.
*
@@ -781,11 +764,149 @@ class expired_contexts_manager {
*/
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.
*
+39 -1
View File
@@ -41,23 +41,33 @@ class expiry_info {
/** @var bool Whether the default expiry value of this purpose has been reached */
protected $defaultexpiryreached = false;
/** @var bool Whether the default purpose is protected */
protected $defaultprotected = false;
/** @var int[] List of expires roles */
protected $expired = [];
/** @var int[] List of unexpires roles */
protected $unexpired = [];
/** @var int[] List of unexpired roles which are also protected */
protected $protectedroles = [];
/**
* Constructor for the expiry_info class.
*
* @param bool $default Whether the default expiry period for this context has been reached.
* @param bool $defaultprotected Whether the default expiry is protected.
* @param int[] $expired A list of roles in this context which have explicitly expired.
* @param int[] $unexpired A list of roles in this context which have not yet expired.
* @param int[] $protectedroles A list of unexpired roles in this context which are protected.
*/
public function __construct(bool $default, array $expired, array $unexpired) {
public function __construct(bool $default, bool $defaultprotected, array $expired, array $unexpired, array $protectedroles) {
$this->defaultexpiryreached = $default;
$this->defaultprotected = $defaultprotected;
$this->expired = $expired;
$this->unexpired = $unexpired;
$this->protectedroles = $protectedroles;
}
/**
@@ -124,6 +134,15 @@ class expiry_info {
return $this->defaultexpiryreached;
}
/**
* Whether the default purpose is protected.
*
* @return bool
*/
public function is_default_protected() : bool {
return $this->defaultprotected;
}
/**
* Get the list of unexpired role IDs.
*
@@ -133,6 +152,25 @@ class expiry_info {
return $this->unexpired;
}
/**
* Get the list of unexpired protected roles.
*
* @return int[]
*/
public function get_unexpired_protected_roles() : array {
return array_keys(array_filter($this->protectedroles));
}
/**
* Get a list of all overridden roles which are unprotected.
* @return int[]
*/
public function get_unprotected_overridden_roles() : array {
$allroles = array_merge($this->expired, $this->unexpired);
return array_diff($allroles, $this->protectedroles);
}
/**
* Merge this expiry_info object with another belonging to a child context in order to set the 'safest' heritage.
*
+287
View File
@@ -24,11 +24,14 @@
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;
@@ -1376,6 +1379,228 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$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
*/
@@ -1635,4 +1860,66 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
'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;
}
}
@@ -29,6 +29,7 @@ use tool_dataprivacy\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;
@@ -50,15 +51,20 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
* @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;
@@ -286,7 +292,6 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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);
@@ -295,7 +300,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -445,7 +450,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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);
@@ -466,8 +471,8 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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();
@@ -485,7 +490,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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);
@@ -522,8 +527,8 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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);
@@ -680,12 +685,11 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -720,13 +724,12 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -760,12 +763,11 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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();
@@ -830,7 +832,6 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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);
@@ -839,7 +840,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -889,12 +890,11 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -978,12 +978,11 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -1067,12 +1066,11 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -1157,11 +1155,10 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -1169,7 +1166,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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',
]);
@@ -1578,7 +1575,7 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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);
@@ -1637,8 +1634,8 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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);
@@ -1722,9 +1719,8 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$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();
@@ -2185,4 +2181,387 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
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));
}
}