moodle/admin/tool/dataprivacy/tests/expired_contexts_test.php
Andrew Nicols b0ea09e200 MDL-63902 dataprivacy: Check course children not the course
When checking the expiry and protected state of a context, we need to do
so knowing what kind of use that context has.

If it is used in the user context, then only the user context matters.
If it is used within a course, then that child context must be checked
in relation to the course.
2018-11-08 09:13:15 +08:00

2624 lines
107 KiB
PHP

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Expired contexts tests.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\expired_context;
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;
/**
* Expired contexts tests.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
/**
* 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($contextlevel)
);
set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
}
return $purpose;
}
/**
* Ensure that a user with no lastaccess is not flagged for deletion.
*/
public function test_flag_not_setup() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with no lastaccess is not flagged for deletion.
*/
public function test_flag_user_no_lastaccess() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with a recent lastaccess is not flagged for deletion.
*/
public function test_flag_user_recent_lastaccess() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time()]);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past is flagged for deletion.
*/
public function test_flag_user_past_lastaccess() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
// Although there is a block in the user context, everything in the user context is regarded as one.
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past but active enrolments is not flagged for deletion.
*/
public function test_flag_user_past_lastaccess_still_enrolled() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enddate' => time() + YEARSECS]);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$otheruser = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past and no active enrolments is flagged for deletion.
*/
public function test_flag_user_update_existing() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'P5Y');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'defaultexpired' => 0,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredcontext->save();
$this->assertEquals(0, $expiredcontext->get('defaultexpired'));
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
// The user context will now have expired.
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(1, $updatedcontext->get('defaultexpired'));
}
/**
* Ensure that a user with a lastaccess in the past and expired enrolments.
*/
public function test_flag_user_past_lastaccess_unexpired_past_enrolment() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'P1Y');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$otheruser = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past and expired enrolments.
*/
public function test_flag_user_past_override_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$systemcontext = \context_system::instance();
$role = $DB->get_record('role', ['shortname' => 'manager']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->user->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$override->save();
role_assign($role->id, $user->id, $systemcontext->id);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
$expiredrecord = expired_context::get_record(['contextid' => $usercontext->id]);
$this->assertFalse($expiredrecord);
}
/**
* Ensure that a user with a lastaccess in the past and expired enrolments.
*/
public function test_flag_user_past_lastaccess_expired_enrolled() {
$this->resetAfterTest();
$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]);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$otheruser = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(1, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected
* correctly.
*/
public function test_flag_user_past_lastaccess_missing_enddate_required() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$course = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$otheruser = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Ensure that course end dates are not required.
set_config('requireallenddatesforuserdeletion', 1, 'tool_dataprivacy');
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected
* correctly when the end date is not required.
*/
public function test_flag_user_past_lastaccess_missing_enddate_not_required() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$course = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$otheruser = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Ensure that course end dates are required.
set_config('requireallenddatesforuserdeletion', 0, 'tool_dataprivacy');
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
}
/**
* Ensure that a user with a recent lastaccess is not flagged for deletion.
*/
public function test_flag_user_recent_lastaccess_existing_record() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time()]);
$usercontext = \context_user::instance($user->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredcontext->save();
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
$this->expectException('dml_missing_record_exception');
new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a user with a recent lastaccess is not flagged for deletion.
*/
public function test_flag_user_retention_changed() {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
$expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
$this->assertNotFalse($expiredcontext);
// Increase the retention period to 5 years.
$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();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
// The expiry record will now have been removed.
$this->expectException('dml_missing_record_exception');
new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a user with a historically expired expired block record child is cleaned up.
*/
public function test_flag_user_historic_block_unapproved() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Create an existing expired_context which has not been approved for the block.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $blockcontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredcontext->save();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
$expiredblockcontext = expired_context::get_record(['contextid' => $blockcontext->id]);
$this->assertFalse($expiredblockcontext);
$expiredusercontext = expired_context::get_record(['contextid' => $usercontext->id]);
$this->assertNotFalse($expiredusercontext);
}
/**
* Ensure that a user with a block which has a default retention period which has not expired, is still expired.
*/
public function test_flag_user_historic_unexpired_child() {
$this->resetAfterTest();
$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->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(1, $flaggedusers);
$expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
$this->assertNotFalse($expiredcontext);
}
/**
* Ensure that a course with no end date is not flagged.
*/
public function test_flag_course_no_enddate() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course();
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the distant past, but a child which is unexpired is not flagged.
*/
public function test_flag_course_past_enddate_future_child() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'P5Y');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the distant past is flagged.
*/
public function test_flag_course_past_enddate() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(2, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the distant past is flagged.
*/
public function test_flag_course_past_enddate_multiple() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course1 = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
$course2 = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(4, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the future is not flagged.
*/
public function test_flag_course_future_enddate() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course(['enddate' => time() + YEARSECS]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the future is not flagged.
*/
public function test_flag_course_recent_unexpired_enddate() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course(['enddate' => time() - 1]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
}
/**
* Ensure that a course with an end date in the distant past is flagged, taking into account any purpose override
*/
public function test_flag_course_past_enddate_with_override_unexpired_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * DAYSECS),
'enddate' => time() - DAYSECS,
]);
$coursecontext = \context_course::instance($course->id);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(1, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
$expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]);
$this->assertEmpty($expiredrecord->get('expiredroles'));
$unexpiredroles = $expiredrecord->get('unexpiredroles');
$this->assertCount(1, $unexpiredroles);
$this->assertContains($role->id, $unexpiredroles);
}
/**
* Ensure that a course with an end date in the distant past is flagged, and any expired role is ignored.
*/
public function test_flag_course_past_enddate_with_override_expired_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$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' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * DAYSECS),
'enddate' => time() - DAYSECS,
]);
$coursecontext = \context_course::instance($course->id);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(1, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
$expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]);
$this->assertEmpty($expiredrecord->get('expiredroles'));
$this->assertEmpty($expiredrecord->get('unexpiredroles'));
$this->assertTrue((bool) $expiredrecord->get('defaultexpired'));
}
/**
* Ensure that where a course has explicitly expired one role, but that role is explicitly not expired in a child
* context, does not have the parent context role expired.
*/
public function test_flag_course_override_expiredwith_override_unexpired_on_child() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('P1Y', 'P1Y', 'P1Y');
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
(new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1S',
]))->save();
$modpurpose = new purpose(0, (object) [
'name' => 'Module purpose',
'retentionperiod' => 'PT1S',
'lawfulbases' => 'gdpr_art_6_1_a',
]);
$modpurpose->create();
(new purpose_override(0, (object) [
'purposeid' => $modpurpose->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]))->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * DAYSECS),
'enddate' => time() - DAYSECS,
]);
$coursecontext = \context_course::instance($course->id);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
api::set_context_instance((object) [
'contextid' => $forumcontext->id,
'purposeid' => $modpurpose->get('id'),
'categoryid' => 0,
]);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(1, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
// The course will not be expired as the default expiry has not passed, and the explicit role override has been
// removed due to the child non-expiry.
$expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]);
$this->assertFalse($expiredrecord);
// The forum will have an expiry for all _but_ the overridden role.
$expiredrecord = expired_context::get_record(['contextid' => $forumcontext->id]);
$this->assertEmpty($expiredrecord->get('expiredroles'));
// The teacher is not expired.
$unexpiredroles = $expiredrecord->get('unexpiredroles');
$this->assertCount(1, $unexpiredroles);
$this->assertContains($role->id, $unexpiredroles);
$this->assertTrue((bool) $expiredrecord->get('defaultexpired'));
}
/**
* Ensure that a user context previously flagged as approved is not removed if the user has any unexpired roles.
*/
public function test_process_user_context_with_override_unexpired_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$systemcontext = \context_system::instance();
$role = $DB->get_record('role', ['shortname' => 'manager']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->user->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$override->save();
role_assign($role->id, $user->id, $systemcontext->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'defaultexpired' => 1,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->add_unexpiredroles([$role->id]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_users_in_context',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$mockprivacymanager->expects($this->never())->method('delete_data_for_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
$manager->set_progress(new \null_progress_trace());
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$this->expectException('dml_missing_record_exception');
$updatedcontext = new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a module context previously flagged as approved is removed with appropriate unexpiredroles kept.
*/
public function test_process_course_context_with_override_unexpired_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'P5Y',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $student->id,
]);
$teacher = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $teacher->id,
]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'defaultexpired' => 1,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->add_unexpiredroles([$role->id]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_users_in_context',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$mockprivacymanager
->expects($this->once())
->method('delete_data_for_users_in_context')
->with($this->callback(function($userlist) use ($student, $teacher) {
$forumlist = $userlist->get_userlist_for_component('mod_forum');
$userids = $forumlist->get_userids();
$this->assertCount(1, $userids);
$this->assertContains($student->id, $userids);
$this->assertNotContains($teacher->id, $userids);
return true;
}));
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
$manager->set_progress(new \null_progress_trace());
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept.
*/
public function test_process_course_context_with_override_expired_role() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
$role = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $student->id,
]);
$teacher = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $teacher->id,
]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'defaultexpired' => 0,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->add_expiredroles([$role->id]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_users_in_context',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$mockprivacymanager
->expects($this->once())
->method('delete_data_for_users_in_context')
->with($this->callback(function($userlist) use ($student, $teacher) {
$forumlist = $userlist->get_userlist_for_component('mod_forum');
$userids = $forumlist->get_userids();
$this->assertCount(1, $userids);
$this->assertContains($student->id, $userids);
$this->assertNotContains($teacher->id, $userids);
return true;
}));
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
$manager->set_progress(new \null_progress_trace());
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept.
*/
public function test_process_course_context_with_user_in_both_lists() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
$role = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $role->id,
'retentionperiod' => 'PT1M',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$teacher = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $teacher->id,
]);
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $student->id,
]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'defaultexpired' => 0,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->add_expiredroles([$role->id]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_users_in_context',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$mockprivacymanager
->expects($this->once())
->method('delete_data_for_users_in_context')
->with($this->callback(function($userlist) use ($student, $teacher) {
$forumlist = $userlist->get_userlist_for_component('mod_forum');
$userids = $forumlist->get_userids();
$this->assertCount(1, $userids);
$this->assertContains($student->id, $userids);
$this->assertNotContains($teacher->id, $userids);
return true;
}));
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
$manager->set_progress(new \null_progress_trace());
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept.
*/
public function test_process_course_context_with_user_in_both_lists_expired() {
global $DB;
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y');
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $studentrole->id,
'retentionperiod' => 'PT1M',
]);
$override->save();
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$override = new purpose_override(0, (object) [
'purposeid' => $purposes->course->get('id'),
'roleid' => $teacherrole->id,
'retentionperiod' => 'PT1M',
]);
$override->save();
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
$teacher = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $teacher->id,
]);
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
$generator->create_discussion((object) [
'course' => $forum->course,
'forum' => $forum->id,
'userid' => $student->id,
]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'defaultexpired' => 0,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->add_expiredroles([$studentrole->id, $teacherrole->id]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_users_in_context',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$mockprivacymanager
->expects($this->once())
->method('delete_data_for_users_in_context')
->with($this->callback(function($userlist) use ($student, $teacher) {
$forumlist = $userlist->get_userlist_for_component('mod_forum');
$userids = $forumlist->get_userids();
$this->assertCount(2, $userids);
$this->assertContains($student->id, $userids);
$this->assertContains($teacher->id, $userids);
return true;
}));
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
$manager->set_progress(new \null_progress_trace());
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a site not setup will not process anything.
*/
public function test_process_not_setup() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
}
/**
* Ensure that a user with no lastaccess is not flagged for deletion.
*/
public function test_process_none_approved() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
}
/**
* Ensure that a user with no lastaccess is not flagged for deletion.
*/
public function test_process_no_context() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => -1,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$this->expectException('dml_missing_record_exception');
new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a user context previously flagged as approved is removed.
*/
public function test_process_user_context() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
$mockprivacymanager->expects($this->exactly(2))
->method('delete_data_for_all_users_in_context')
->withConsecutive(
[$blockcontext],
[$usercontext]
);
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(1, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
// Flag all expired contexts again.
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(0, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
// Ensure that the deleted context record is still present.
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a course context previously flagged as approved is removed.
*/
public function test_process_course_context() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$coursecontext = \context_course::instance($course->id);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $coursecontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->once())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a user context previously flagged as approved is not removed if the user then logs in.
*/
public function test_process_user_context_logged_in_after_approval() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
// Now bump the user's last login time.
$this->setUser($user);
user_accesstime_log();
$this->setUser();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$this->expectException('dml_missing_record_exception');
new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a user context previously flagged as approved is not removed if the purpose has changed.
*/
public function test_process_user_context_changed_after_approved() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$context = \context_block::instance($block->instance->id);
$this->setUser();
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
// Now make the user a site admin.
$admins = explode(',', get_config('moodle', 'siteadmins'));
$admins[] = $user->id;
set_config('siteadmins', implode(',', $admins));
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$this->expectException('dml_missing_record_exception');
new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a user with a historically expired expired block record child is cleaned up.
*/
public function test_process_user_historic_block_unapproved() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H');
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
$usercontext = \context_user::instance($user->id);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Create an expired_context for the user.
$expiredusercontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredusercontext->save();
// Create an existing expired_context which has not been approved for the block.
$expiredblockcontext = new expired_context(0, (object) [
'contextid' => $blockcontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredblockcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
$mockprivacymanager->expects($this->exactly(2))
->method('delete_data_for_all_users_in_context')
->withConsecutive(
[$blockcontext],
[$usercontext]
);
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(1, $processedusers);
$updatedcontext = new expired_context($expiredusercontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a user with a block which has a default retention period which has not expired, is still expired.
*/
public function test_process_user_historic_unexpired_child() {
$this->resetAfterTest();
$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->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Create an expired_context for the user.
$expiredusercontext = new expired_context(0, (object) [
'contextid' => $usercontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredusercontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
$mockprivacymanager->expects($this->exactly(2))
->method('delete_data_for_all_users_in_context')
->withConsecutive(
[$blockcontext],
[$usercontext]
);
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(1, $processedusers);
$updatedcontext = new expired_context($expiredusercontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
* updated.
*/
public function test_process_course_context_updated() {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$coursecontext = \context_course::instance($course->id);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $coursecontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
// Changing the retention period to a longer period will remove the expired_context record.
$purposes->activity->set('retentionperiod', 'P5Y');
$purposes->activity->save();
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$this->expectException('dml_missing_record_exception');
$updatedcontext = new expired_context($expiredcontext->get('id'));
}
/**
* Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
* updated.
*/
public function test_process_course_context_outstanding_children() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$coursecontext = \context_course::instance($course->id);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
// Create an existing expired_context.
$expiredcontext = new expired_context(0, (object) [
'contextid' => $coursecontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcontext->get('id'));
// No change - we just can't process it until the children have finished.
$this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
}
/**
* Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
* updated.
*/
public function test_process_course_context_pending_children() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$coursecontext = \context_course::instance($course->id);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
// Create an existing expired_context for the course.
$expiredcoursecontext = new expired_context(0, (object) [
'contextid' => $coursecontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcoursecontext->save();
// And for the forum.
$expiredforumcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'status' => expired_context::STATUS_EXPIRED,
]);
$expiredforumcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(0, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcoursecontext->get('id'));
// No change - we just can't process it until the children have finished.
$this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
}
/**
* Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
* updated.
*/
public function test_process_course_context_approved_children() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$coursecontext = \context_course::instance($course->id);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
// Create an existing expired_context for the course.
$expiredcoursecontext = new expired_context(0, (object) [
'contextid' => $coursecontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredcoursecontext->save();
// And for the forum.
$expiredforumcontext = new expired_context(0, (object) [
'contextid' => $forumcontext->id,
'status' => expired_context::STATUS_APPROVED,
]);
$expiredforumcontext->save();
$mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods([
'delete_data_for_user',
'delete_data_for_all_users_in_context',
])
->getMock();
$mockprivacymanager->expects($this->never())->method('delete_data_for_user');
$mockprivacymanager->expects($this->exactly(2))
->method('delete_data_for_all_users_in_context')
->withConsecutive(
[$forumcontext],
[$coursecontext]
);
$manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
->setMethods(['get_privacy_manager'])
->getMock();
$manager->set_progress(new \null_progress_trace());
$manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
// Initially only the forum will be processed.
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredforumcontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
// The course won't have been processed yet.
$updatedcontext = new expired_context($expiredcoursecontext->get('id'));
$this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
// A subsequent run will cause the course to processed as it is no longer dependent upon the child contexts.
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
$updatedcontext = new expired_context($expiredcoursecontext->get('id'));
$this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
}
/**
* Test that the can_process_deletion function returns expected results.
*
* @dataProvider can_process_deletion_provider
* @param int $status
* @param bool $expected
*/
public function test_can_process_deletion($status, $expected) {
$purpose = new expired_context(0, (object) [
'status' => $status,
'contextid' => \context_system::instance()->id,
]);
$this->assertEquals($expected, $purpose->can_process_deletion());
}
/**
* Data provider for the can_process_deletion tests.
*
* @return array
*/
public function can_process_deletion_provider() : array {
return [
'Pending' => [
expired_context::STATUS_EXPIRED,
false,
],
'Approved' => [
expired_context::STATUS_APPROVED,
true,
],
'Complete' => [
expired_context::STATUS_CLEANED,
false,
],
];
}
/**
* Test that the is_complete function returns expected results.
*
* @dataProvider is_complete_provider
* @param int $status
* @param bool $expected
*/
public function test_is_complete($status, $expected) {
$purpose = new expired_context(0, (object) [
'status' => $status,
'contextid' => \context_system::instance()->id,
]);
$this->assertEquals($expected, $purpose->is_complete());
}
/**
* Data provider for the is_complete tests.
*
* @return array
*/
public function is_complete_provider() : array {
return [
'Pending' => [
expired_context::STATUS_EXPIRED,
false,
],
'Approved' => [
expired_context::STATUS_APPROVED,
false,
],
'Complete' => [
expired_context::STATUS_CLEANED,
true,
],
];
}
/**
* Test that the is_fully_expired function returns expected results.
*
* @dataProvider is_fully_expired_provider
* @param array $record
* @param bool $expected
*/
public function test_is_fully_expired($record, $expected) {
$purpose = new expired_context(0, (object) $record);
$this->assertEquals($expected, $purpose->is_fully_expired());
}
/**
* Data provider for the is_fully_expired tests.
*
* @return array
*/
public function is_fully_expired_provider() : array {
return [
'Fully expired' => [
[
'status' => expired_context::STATUS_APPROVED,
'defaultexpired' => 1,
],
true,
],
'Unexpired roles present' => [
[
'status' => expired_context::STATUS_APPROVED,
'defaultexpired' => 1,
'unexpiredroles' => json_encode([1]),
],
false,
],
'Only some expired roles present' => [
[
'status' => expired_context::STATUS_APPROVED,
'defaultexpired' => 0,
'expiredroles' => json_encode([1]),
],
false,
],
];
}
/**
* Ensure that any orphaned records are removed once the context has been removed.
*/
public function test_orphaned_records_are_cleared() {
$this->resetAfterTest();
$this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
$course = $this->getDataGenerator()->create_course([
'startdate' => time() - (2 * YEARSECS),
'enddate' => time() - YEARSECS,
]);
$context = \context_course::instance($course->id);
// Flag all expired contexts.
$manager = new \tool_dataprivacy\expired_contexts_manager();
$manager->set_progress(new \null_progress_trace());
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$this->assertEquals(1, $flaggedcourses);
$this->assertEquals(0, $flaggedusers);
// Ensure that the record currently exists.
$expiredcontext = expired_context::get_record(['contextid' => $context->id]);
$this->assertNotFalse($expiredcontext);
// Approve it.
$expiredcontext->set('status', expired_context::STATUS_APPROVED)->save();
// Process deletions.
list($processedcourses, $processedusers) = $manager->process_approved_deletions();
$this->assertEquals(1, $processedcourses);
$this->assertEquals(0, $processedusers);
// Ensure that the record still exists.
$expiredcontext = expired_context::get_record(['contextid' => $context->id]);
$this->assertNotFalse($expiredcontext);
// Remove the actual course.
delete_course($course->id, false);
// The record will still exist until we flag it again.
$expiredcontext = expired_context::get_record(['contextid' => $context->id]);
$this->assertNotFalse($expiredcontext);
list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
$expiredcontext = expired_context::get_record(['contextid' => $context->id]);
$this->assertFalse($expiredcontext);
}
/**
* Ensure that the progres tracer works as expected out of the box.
*/
public function test_progress_tracer_default() {
$manager = new \tool_dataprivacy\expired_contexts_manager();
$rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
$rcm = $rc->getMethod('get_progress');
$rcm->setAccessible(true);
$this->assertInstanceOf(\text_progress_trace::class, $rcm->invoke($manager));
}
/**
* Ensure that the progres tracer works as expected when given a specific traer.
*/
public function test_progress_tracer_set() {
$manager = new \tool_dataprivacy\expired_contexts_manager();
$mytrace = new \null_progress_trace();
$manager->set_progress($mytrace);
$rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
$rcm = $rc->getMethod('get_progress');
$rcm->setAccessible(true);
$this->assertSame($mytrace, $rcm->invoke($manager));
}
/**
* Creates an HTML block on a user.
*
* @param string $title
* @param string $body
* @param string $format
* @return \block_instance
*/
protected function create_user_block($title, $body, $format) {
global $USER;
$configdata = (object) [
'title' => $title,
'text' => [
'itemid' => 19,
'text' => $body,
'format' => $format,
],
];
$this->create_block($this->construct_user_page($USER));
$block = $this->get_last_block_on_page($this->construct_user_page($USER));
$block = block_instance('html', $block->instance);
$block->instance_config_save((object) $configdata);
return $block;
}
/**
* Creates an HTML block on a page.
*
* @param \page $page Page
*/
protected function create_block($page) {
$page->blocks->add_block_at_end_of_default_region('html');
}
/**
* Constructs a Page object for the User Dashboard.
*
* @param \stdClass $user User to create Dashboard for.
* @return \moodle_page
*/
protected function construct_user_page(\stdClass $user) {
$page = new \moodle_page();
$page->set_context(\context_user::instance($user->id));
$page->set_pagelayout('mydashboard');
$page->set_pagetype('my-index');
$page->blocks->load_blocks();
return $page;
}
/**
* Get the last block on the page.
*
* @param \page $page Page
* @return \block_html Block instance object
*/
protected function get_last_block_on_page($page) {
$blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
$block = end($blocks);
return $block;
}
/**
* Test the is_context_expired functions when supplied with the system context.
*/
public function test_is_context_expired_system() {
$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 a block in the user context.
*
* Children of a user context always follow the user expiry rather than any context level defaults (e.g. at the
* block level.
*/
public function test_is_context_expired_user_block() {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
$purposes->block = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$this->setUser($user);
$block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
$blockcontext = \context_block::instance($block->instance->id);
$this->setUser();
// Protected flags have no bearing on expiry of user subcontexts.
$this->assertTrue(expired_contexts_manager::is_context_expired($blockcontext));
$purposes->block->set('protected', 1)->save();
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
$purposes->block->set('protected', 0)->save();
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
}
/**
* Test the is_context_expired functions when supplied with an expired course.
*/
public function test_is_context_expired_course_expired() {
$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() {
$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 unexpired course and a child context in the course which is protected.
*
* When a child context has a specific purpose set, then that purpose should be respected with respect to the
* course.
*
* If the course is still within the expiry period for the child context, then that child's protected flag should be
* respected, even when the course may have expired.
*/
public function test_is_child_context_expired_course_unexpired_with_child() {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D', 'P1D');
$purposes->course->set('protected', 0)->save();
$purposes->activity->set('protected', 1)->save();
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() + WEEKSECS]);
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$coursecontext = \context_course::instance($course->id);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$forumcontext = \context_module::instance($cm->id);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
$this->assertFalse(expired_contexts_manager::is_context_expired($forumcontext));
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
$this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $user));
$purposes->activity->set('protected', 0)->save();
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $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));
}
}