See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Expired contexts tests. 19 * 20 * @package tool_dataprivacy 21 * @copyright 2018 David Monllao 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 use tool_dataprivacy\api; 26 use tool_dataprivacy\data_registry; 27 use tool_dataprivacy\expired_context; 28 use tool_dataprivacy\purpose; 29 use tool_dataprivacy\purpose_override; 30 use tool_dataprivacy\category; 31 use tool_dataprivacy\contextlevel; 32 use tool_dataprivacy\expired_contexts_manager; 33 34 defined('MOODLE_INTERNAL') || die(); 35 global $CFG; 36 37 /** 38 * Expired contexts tests. 39 * 40 * @package tool_dataprivacy 41 * @copyright 2018 David Monllao 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase { 45 46 /** 47 * Setup the basics with the specified retention period. 48 * 49 * @param string $system Retention policy for the system. 50 * @param string $user Retention policy for users. 51 * @param string $course Retention policy for courses. 52 * @param string $activity Retention policy for activities. 53 */ 54 protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass { 55 $this->resetAfterTest(); 56 57 $purposes = (object) [ 58 'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM), 59 'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER), 60 ]; 61 62 if (null !== $course) { 63 $purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE); 64 } 65 66 if (null !== $activity) { 67 $purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE); 68 } 69 70 return $purposes; 71 } 72 73 /** 74 * Create a retention period and set it for the specified context level. 75 * 76 * @param string $retention 77 * @param int $contextlevel 78 * @return purpose 79 */ 80 protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) : purpose { 81 $purpose = new purpose(0, (object) [ 82 'name' => 'Test purpose ' . rand(1, 1000), 83 'retentionperiod' => $retention, 84 'lawfulbases' => 'gdpr_art_6_1_a', 85 ]); 86 $purpose->create(); 87 88 $cat = new category(0, (object) ['name' => 'Test category']); 89 $cat->create(); 90 91 if ($contextlevel <= CONTEXT_USER) { 92 $record = (object) [ 93 'purposeid' => $purpose->get('id'), 94 'categoryid' => $cat->get('id'), 95 'contextlevel' => $contextlevel, 96 ]; 97 api::set_contextlevel($record); 98 } else { 99 list($purposevar, ) = data_registry::var_names_from_context( 100 \context_helper::get_class_for_level($contextlevel) 101 ); 102 set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy'); 103 } 104 105 return $purpose; 106 } 107 108 /** 109 * Ensure that a user with no lastaccess is not flagged for deletion. 110 */ 111 public function test_flag_not_setup() { 112 $this->resetAfterTest(); 113 114 $user = $this->getDataGenerator()->create_user(); 115 116 $this->setUser($user); 117 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 118 $context = \context_block::instance($block->instance->id); 119 $this->setUser(); 120 121 // Flag all expired contexts. 122 $manager = new \tool_dataprivacy\expired_contexts_manager(); 123 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 124 125 $this->assertEquals(0, $flaggedcourses); 126 $this->assertEquals(0, $flaggedusers); 127 } 128 129 /** 130 * Ensure that a user with no lastaccess is not flagged for deletion. 131 */ 132 public function test_flag_user_no_lastaccess() { 133 $this->resetAfterTest(); 134 135 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 136 137 $user = $this->getDataGenerator()->create_user(); 138 139 $this->setUser($user); 140 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 141 $context = \context_block::instance($block->instance->id); 142 $this->setUser(); 143 144 // Flag all expired contexts. 145 $manager = new \tool_dataprivacy\expired_contexts_manager(); 146 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 147 148 $this->assertEquals(0, $flaggedcourses); 149 $this->assertEquals(0, $flaggedusers); 150 } 151 152 /** 153 * Ensure that a user with a recent lastaccess is not flagged for deletion. 154 */ 155 public function test_flag_user_recent_lastaccess() { 156 $this->resetAfterTest(); 157 158 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 159 160 $user = $this->getDataGenerator()->create_user(['lastaccess' => time()]); 161 162 $this->setUser($user); 163 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 164 $context = \context_block::instance($block->instance->id); 165 $this->setUser(); 166 167 // Flag all expired contexts. 168 $manager = new \tool_dataprivacy\expired_contexts_manager(); 169 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 170 171 $this->assertEquals(0, $flaggedcourses); 172 $this->assertEquals(0, $flaggedusers); 173 } 174 175 /** 176 * Ensure that a user with a lastaccess in the past is flagged for deletion. 177 */ 178 public function test_flag_user_past_lastaccess() { 179 $this->resetAfterTest(); 180 181 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 182 183 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 184 185 $this->setUser($user); 186 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 187 $context = \context_block::instance($block->instance->id); 188 $this->setUser(); 189 190 // Flag all expired contexts. 191 $manager = new \tool_dataprivacy\expired_contexts_manager(); 192 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 193 194 // Although there is a block in the user context, everything in the user context is regarded as one. 195 $this->assertEquals(0, $flaggedcourses); 196 $this->assertEquals(1, $flaggedusers); 197 } 198 199 /** 200 * Ensure that a user with a lastaccess in the past but active enrolments is not flagged for deletion. 201 */ 202 public function test_flag_user_past_lastaccess_still_enrolled() { 203 $this->resetAfterTest(); 204 205 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 206 207 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 208 $course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enddate' => time() + YEARSECS]); 209 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 210 211 $otheruser = $this->getDataGenerator()->create_user(); 212 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student'); 213 214 $this->setUser($user); 215 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 216 $context = \context_block::instance($block->instance->id); 217 $this->setUser(); 218 219 // Flag all expired contexts. 220 $manager = new \tool_dataprivacy\expired_contexts_manager(); 221 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 222 223 $this->assertEquals(0, $flaggedcourses); 224 $this->assertEquals(0, $flaggedusers); 225 } 226 227 /** 228 * Ensure that a user with a lastaccess in the past and no active enrolments is flagged for deletion. 229 */ 230 public function test_flag_user_update_existing() { 231 $this->resetAfterTest(); 232 233 $this->setup_basics('PT1H', 'PT1H', 'P5Y'); 234 235 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 236 $usercontext = \context_user::instance($user->id); 237 238 // Create an existing expired_context. 239 $expiredcontext = new expired_context(0, (object) [ 240 'contextid' => $usercontext->id, 241 'defaultexpired' => 0, 242 'status' => expired_context::STATUS_EXPIRED, 243 ]); 244 $expiredcontext->save(); 245 $this->assertEquals(0, $expiredcontext->get('defaultexpired')); 246 247 // Flag all expired contexts. 248 $manager = new \tool_dataprivacy\expired_contexts_manager(); 249 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 250 251 $this->assertEquals(0, $flaggedcourses); 252 $this->assertEquals(1, $flaggedusers); 253 254 // The user context will now have expired. 255 $updatedcontext = new expired_context($expiredcontext->get('id')); 256 $this->assertEquals(1, $updatedcontext->get('defaultexpired')); 257 } 258 259 /** 260 * Ensure that a user with a lastaccess in the past and expired enrolments. 261 */ 262 public function test_flag_user_past_lastaccess_unexpired_past_enrolment() { 263 $this->resetAfterTest(); 264 265 $this->setup_basics('PT1H', 'PT1H', 'P1Y'); 266 267 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 268 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 269 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 270 271 $otheruser = $this->getDataGenerator()->create_user(); 272 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student'); 273 274 $this->setUser($user); 275 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 276 $context = \context_block::instance($block->instance->id); 277 $this->setUser(); 278 279 // Flag all expired contexts. 280 $manager = new \tool_dataprivacy\expired_contexts_manager(); 281 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 282 283 $this->assertEquals(0, $flaggedcourses); 284 $this->assertEquals(0, $flaggedusers); 285 } 286 287 /** 288 * Ensure that a user with a lastaccess in the past and expired enrolments. 289 */ 290 public function test_flag_user_past_override_role() { 291 global $DB; 292 $this->resetAfterTest(); 293 294 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 295 296 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 297 $usercontext = \context_user::instance($user->id); 298 $systemcontext = \context_system::instance(); 299 300 $role = $DB->get_record('role', ['shortname' => 'manager']); 301 302 $override = new purpose_override(0, (object) [ 303 'purposeid' => $purposes->user->get('id'), 304 'roleid' => $role->id, 305 'retentionperiod' => 'P5Y', 306 ]); 307 $override->save(); 308 role_assign($role->id, $user->id, $systemcontext->id); 309 310 // Flag all expired contexts. 311 $manager = new \tool_dataprivacy\expired_contexts_manager(); 312 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 313 314 $this->assertEquals(0, $flaggedcourses); 315 $this->assertEquals(0, $flaggedusers); 316 317 $expiredrecord = expired_context::get_record(['contextid' => $usercontext->id]); 318 $this->assertFalse($expiredrecord); 319 } 320 321 /** 322 * Ensure that a user with a lastaccess in the past and expired enrolments. 323 */ 324 public function test_flag_user_past_lastaccess_expired_enrolled() { 325 $this->resetAfterTest(); 326 327 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 328 329 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 330 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 331 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 332 333 $otheruser = $this->getDataGenerator()->create_user(); 334 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student'); 335 336 $this->setUser($user); 337 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 338 $context = \context_block::instance($block->instance->id); 339 $this->setUser(); 340 341 // Flag all expired contexts. 342 $manager = new \tool_dataprivacy\expired_contexts_manager(); 343 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 344 345 $this->assertEquals(1, $flaggedcourses); 346 $this->assertEquals(1, $flaggedusers); 347 } 348 349 /** 350 * Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected 351 * correctly. 352 */ 353 public function test_flag_user_past_lastaccess_missing_enddate_required() { 354 $this->resetAfterTest(); 355 356 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 357 358 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 359 $course = $this->getDataGenerator()->create_course(); 360 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 361 362 $otheruser = $this->getDataGenerator()->create_user(); 363 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student'); 364 365 $this->setUser($user); 366 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 367 $context = \context_block::instance($block->instance->id); 368 $this->setUser(); 369 370 // Ensure that course end dates are not required. 371 set_config('requireallenddatesforuserdeletion', 1, 'tool_dataprivacy'); 372 373 // Flag all expired contexts. 374 $manager = new \tool_dataprivacy\expired_contexts_manager(); 375 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 376 377 $this->assertEquals(0, $flaggedcourses); 378 $this->assertEquals(0, $flaggedusers); 379 } 380 381 /** 382 * Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected 383 * correctly when the end date is not required. 384 */ 385 public function test_flag_user_past_lastaccess_missing_enddate_not_required() { 386 $this->resetAfterTest(); 387 388 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 389 390 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 391 $course = $this->getDataGenerator()->create_course(); 392 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 393 394 $otheruser = $this->getDataGenerator()->create_user(); 395 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student'); 396 397 $this->setUser($user); 398 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 399 $context = \context_block::instance($block->instance->id); 400 $this->setUser(); 401 402 // Ensure that course end dates are required. 403 set_config('requireallenddatesforuserdeletion', 0, 'tool_dataprivacy'); 404 405 // Flag all expired contexts. 406 $manager = new \tool_dataprivacy\expired_contexts_manager(); 407 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 408 409 $this->assertEquals(0, $flaggedcourses); 410 $this->assertEquals(1, $flaggedusers); 411 } 412 413 /** 414 * Ensure that a user with a recent lastaccess is not flagged for deletion. 415 */ 416 public function test_flag_user_recent_lastaccess_existing_record() { 417 $this->resetAfterTest(); 418 419 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 420 421 $user = $this->getDataGenerator()->create_user(['lastaccess' => time()]); 422 $usercontext = \context_user::instance($user->id); 423 424 // Create an existing expired_context. 425 $expiredcontext = new expired_context(0, (object) [ 426 'contextid' => $usercontext->id, 427 'status' => expired_context::STATUS_EXPIRED, 428 ]); 429 $expiredcontext->save(); 430 431 $this->setUser($user); 432 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 433 $context = \context_block::instance($block->instance->id); 434 $this->setUser(); 435 436 // Flag all expired contexts. 437 $manager = new \tool_dataprivacy\expired_contexts_manager(); 438 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 439 440 $this->assertEquals(0, $flaggedcourses); 441 $this->assertEquals(0, $flaggedusers); 442 443 $this->expectException('dml_missing_record_exception'); 444 new expired_context($expiredcontext->get('id')); 445 } 446 447 /** 448 * Ensure that a user with a recent lastaccess is not flagged for deletion. 449 */ 450 public function test_flag_user_retention_changed() { 451 $this->resetAfterTest(); 452 453 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 454 455 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]); 456 $usercontext = \context_user::instance($user->id); 457 458 $this->setUser($user); 459 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 460 $context = \context_block::instance($block->instance->id); 461 $this->setUser(); 462 463 // Flag all expired contexts. 464 $manager = new \tool_dataprivacy\expired_contexts_manager(); 465 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 466 467 $this->assertEquals(0, $flaggedcourses); 468 $this->assertEquals(1, $flaggedusers); 469 470 $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]); 471 $this->assertNotFalse($expiredcontext); 472 473 // Increase the retention period to 5 years. 474 $purposes->user->set('retentionperiod', 'P5Y'); 475 $purposes->user->save(); 476 477 // Re-run the expiry job - the previously flagged user will be removed because the retention period has been increased. 478 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 479 $this->assertEquals(0, $flaggedcourses); 480 $this->assertEquals(0, $flaggedusers); 481 482 // The expiry record will now have been removed. 483 $this->expectException('dml_missing_record_exception'); 484 new expired_context($expiredcontext->get('id')); 485 } 486 487 /** 488 * Ensure that a user with a historically expired expired block record child is cleaned up. 489 */ 490 public function test_flag_user_historic_block_unapproved() { 491 $this->resetAfterTest(); 492 493 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 494 495 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]); 496 $usercontext = \context_user::instance($user->id); 497 498 $this->setUser($user); 499 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 500 $blockcontext = \context_block::instance($block->instance->id); 501 $this->setUser(); 502 503 // Create an existing expired_context which has not been approved for the block. 504 $expiredcontext = new expired_context(0, (object) [ 505 'contextid' => $blockcontext->id, 506 'status' => expired_context::STATUS_EXPIRED, 507 ]); 508 $expiredcontext->save(); 509 510 // Flag all expired contexts. 511 $manager = new \tool_dataprivacy\expired_contexts_manager(); 512 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 513 514 $this->assertEquals(0, $flaggedcourses); 515 $this->assertEquals(1, $flaggedusers); 516 517 $expiredblockcontext = expired_context::get_record(['contextid' => $blockcontext->id]); 518 $this->assertFalse($expiredblockcontext); 519 520 $expiredusercontext = expired_context::get_record(['contextid' => $usercontext->id]); 521 $this->assertNotFalse($expiredusercontext); 522 } 523 524 /** 525 * Ensure that a user with a block which has a default retention period which has not expired, is still expired. 526 */ 527 public function test_flag_user_historic_unexpired_child() { 528 $this->resetAfterTest(); 529 530 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 531 $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK); 532 533 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]); 534 $usercontext = \context_user::instance($user->id); 535 536 $this->setUser($user); 537 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 538 $blockcontext = \context_block::instance($block->instance->id); 539 $this->setUser(); 540 541 // Flag all expired contexts. 542 $manager = new \tool_dataprivacy\expired_contexts_manager(); 543 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 544 545 $this->assertEquals(0, $flaggedcourses); 546 $this->assertEquals(1, $flaggedusers); 547 548 $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]); 549 $this->assertNotFalse($expiredcontext); 550 } 551 552 /** 553 * Ensure that a course with no end date is not flagged. 554 */ 555 public function test_flag_course_no_enddate() { 556 $this->resetAfterTest(); 557 558 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 559 560 $course = $this->getDataGenerator()->create_course(); 561 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 562 563 // Flag all expired contexts. 564 $manager = new \tool_dataprivacy\expired_contexts_manager(); 565 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 566 567 $this->assertEquals(0, $flaggedcourses); 568 $this->assertEquals(0, $flaggedusers); 569 } 570 571 /** 572 * Ensure that a course with an end date in the distant past, but a child which is unexpired is not flagged. 573 */ 574 public function test_flag_course_past_enddate_future_child() { 575 $this->resetAfterTest(); 576 577 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'P5Y'); 578 579 $course = $this->getDataGenerator()->create_course([ 580 'startdate' => time() - (2 * YEARSECS), 581 'enddate' => time() - YEARSECS, 582 ]); 583 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 584 585 // Flag all expired contexts. 586 $manager = new \tool_dataprivacy\expired_contexts_manager(); 587 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 588 589 $this->assertEquals(0, $flaggedcourses); 590 $this->assertEquals(0, $flaggedusers); 591 } 592 593 /** 594 * Ensure that a course with an end date in the distant past is flagged. 595 */ 596 public function test_flag_course_past_enddate() { 597 $this->resetAfterTest(); 598 599 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 600 601 $course = $this->getDataGenerator()->create_course([ 602 'startdate' => time() - (2 * YEARSECS), 603 'enddate' => time() - YEARSECS, 604 ]); 605 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 606 607 // Flag all expired contexts. 608 $manager = new \tool_dataprivacy\expired_contexts_manager(); 609 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 610 611 $this->assertEquals(2, $flaggedcourses); 612 $this->assertEquals(0, $flaggedusers); 613 } 614 615 /** 616 * Ensure that a course with an end date in the distant past is flagged. 617 */ 618 public function test_flag_course_past_enddate_multiple() { 619 $this->resetAfterTest(); 620 621 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 622 623 $course1 = $this->getDataGenerator()->create_course([ 624 'startdate' => time() - (2 * YEARSECS), 625 'enddate' => time() - YEARSECS, 626 ]); 627 $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]); 628 629 $course2 = $this->getDataGenerator()->create_course([ 630 'startdate' => time() - (2 * YEARSECS), 631 'enddate' => time() - YEARSECS, 632 ]); 633 $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]); 634 635 // Flag all expired contexts. 636 $manager = new \tool_dataprivacy\expired_contexts_manager(); 637 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 638 639 $this->assertEquals(4, $flaggedcourses); 640 $this->assertEquals(0, $flaggedusers); 641 } 642 643 /** 644 * Ensure that a course with an end date in the future is not flagged. 645 */ 646 public function test_flag_course_future_enddate() { 647 $this->resetAfterTest(); 648 649 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 650 651 $course = $this->getDataGenerator()->create_course(['enddate' => time() + YEARSECS]); 652 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 653 654 // Flag all expired contexts. 655 $manager = new \tool_dataprivacy\expired_contexts_manager(); 656 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 657 658 $this->assertEquals(0, $flaggedcourses); 659 $this->assertEquals(0, $flaggedusers); 660 } 661 662 /** 663 * Ensure that a course with an end date in the future is not flagged. 664 */ 665 public function test_flag_course_recent_unexpired_enddate() { 666 $this->resetAfterTest(); 667 668 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 669 670 $course = $this->getDataGenerator()->create_course(['enddate' => time() - 1]); 671 672 // Flag all expired contexts. 673 $manager = new \tool_dataprivacy\expired_contexts_manager(); 674 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 675 676 $this->assertEquals(0, $flaggedcourses); 677 $this->assertEquals(0, $flaggedusers); 678 } 679 680 /** 681 * Ensure that a course with an end date in the distant past is flagged, taking into account any purpose override 682 */ 683 public function test_flag_course_past_enddate_with_override_unexpired_role() { 684 global $DB; 685 $this->resetAfterTest(); 686 687 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 688 689 $role = $DB->get_record('role', ['shortname' => 'editingteacher']); 690 691 $override = new purpose_override(0, (object) [ 692 'purposeid' => $purposes->course->get('id'), 693 'roleid' => $role->id, 694 'retentionperiod' => 'P5Y', 695 ]); 696 $override->save(); 697 698 $course = $this->getDataGenerator()->create_course([ 699 'startdate' => time() - (2 * DAYSECS), 700 'enddate' => time() - DAYSECS, 701 ]); 702 $coursecontext = \context_course::instance($course->id); 703 704 // Flag all expired contexts. 705 $manager = new \tool_dataprivacy\expired_contexts_manager(); 706 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 707 708 $this->assertEquals(1, $flaggedcourses); 709 $this->assertEquals(0, $flaggedusers); 710 711 $expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]); 712 $this->assertEmpty($expiredrecord->get('expiredroles')); 713 714 $unexpiredroles = $expiredrecord->get('unexpiredroles'); 715 $this->assertCount(1, $unexpiredroles); 716 $this->assertContains($role->id, $unexpiredroles); 717 } 718 719 /** 720 * Ensure that a course with an end date in the distant past is flagged, and any expired role is ignored. 721 */ 722 public function test_flag_course_past_enddate_with_override_expired_role() { 723 global $DB; 724 $this->resetAfterTest(); 725 726 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 727 728 $role = $DB->get_record('role', ['shortname' => 'student']); 729 730 // The role has a much shorter retention, but both should match. 731 $override = new purpose_override(0, (object) [ 732 'purposeid' => $purposes->course->get('id'), 733 'roleid' => $role->id, 734 'retentionperiod' => 'PT1M', 735 ]); 736 $override->save(); 737 738 $course = $this->getDataGenerator()->create_course([ 739 'startdate' => time() - (2 * DAYSECS), 740 'enddate' => time() - DAYSECS, 741 ]); 742 $coursecontext = \context_course::instance($course->id); 743 744 // Flag all expired contexts. 745 $manager = new \tool_dataprivacy\expired_contexts_manager(); 746 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 747 748 $this->assertEquals(1, $flaggedcourses); 749 $this->assertEquals(0, $flaggedusers); 750 751 $expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]); 752 $this->assertEmpty($expiredrecord->get('expiredroles')); 753 $this->assertEmpty($expiredrecord->get('unexpiredroles')); 754 $this->assertTrue((bool) $expiredrecord->get('defaultexpired')); 755 } 756 757 /** 758 * Ensure that where a course has explicitly expired one role, but that role is explicitly not expired in a child 759 * context, does not have the parent context role expired. 760 */ 761 public function test_flag_course_override_expiredwith_override_unexpired_on_child() { 762 global $DB; 763 $this->resetAfterTest(); 764 765 $purposes = $this->setup_basics('P1Y', 'P1Y', 'P1Y'); 766 767 $role = $DB->get_record('role', ['shortname' => 'editingteacher']); 768 769 (new purpose_override(0, (object) [ 770 'purposeid' => $purposes->course->get('id'), 771 'roleid' => $role->id, 772 'retentionperiod' => 'PT1S', 773 ]))->save(); 774 775 $modpurpose = new purpose(0, (object) [ 776 'name' => 'Module purpose', 777 'retentionperiod' => 'PT1S', 778 'lawfulbases' => 'gdpr_art_6_1_a', 779 ]); 780 $modpurpose->create(); 781 782 (new purpose_override(0, (object) [ 783 'purposeid' => $modpurpose->get('id'), 784 'roleid' => $role->id, 785 'retentionperiod' => 'P5Y', 786 ]))->save(); 787 788 $course = $this->getDataGenerator()->create_course([ 789 'startdate' => time() - (2 * DAYSECS), 790 'enddate' => time() - DAYSECS, 791 ]); 792 $coursecontext = \context_course::instance($course->id); 793 794 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 795 $cm = get_coursemodule_from_instance('forum', $forum->id); 796 $forumcontext = \context_module::instance($cm->id); 797 798 api::set_context_instance((object) [ 799 'contextid' => $forumcontext->id, 800 'purposeid' => $modpurpose->get('id'), 801 'categoryid' => 0, 802 ]); 803 804 // Flag all expired contexts. 805 $manager = new \tool_dataprivacy\expired_contexts_manager(); 806 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 807 808 $this->assertEquals(1, $flaggedcourses); 809 $this->assertEquals(0, $flaggedusers); 810 811 // The course will not be expired as the default expiry has not passed, and the explicit role override has been 812 // removed due to the child non-expiry. 813 $expiredrecord = expired_context::get_record(['contextid' => $coursecontext->id]); 814 $this->assertFalse($expiredrecord); 815 816 // The forum will have an expiry for all _but_ the overridden role. 817 $expiredrecord = expired_context::get_record(['contextid' => $forumcontext->id]); 818 $this->assertEmpty($expiredrecord->get('expiredroles')); 819 820 // The teacher is not expired. 821 $unexpiredroles = $expiredrecord->get('unexpiredroles'); 822 $this->assertCount(1, $unexpiredroles); 823 $this->assertContains($role->id, $unexpiredroles); 824 $this->assertTrue((bool) $expiredrecord->get('defaultexpired')); 825 } 826 827 /** 828 * Ensure that a user context previously flagged as approved is not removed if the user has any unexpired roles. 829 */ 830 public function test_process_user_context_with_override_unexpired_role() { 831 global $DB; 832 $this->resetAfterTest(); 833 834 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 835 836 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 837 $usercontext = \context_user::instance($user->id); 838 $systemcontext = \context_system::instance(); 839 840 $role = $DB->get_record('role', ['shortname' => 'manager']); 841 842 $override = new purpose_override(0, (object) [ 843 'purposeid' => $purposes->user->get('id'), 844 'roleid' => $role->id, 845 'retentionperiod' => 'P5Y', 846 ]); 847 $override->save(); 848 role_assign($role->id, $user->id, $systemcontext->id); 849 850 // Create an existing expired_context. 851 $expiredcontext = new expired_context(0, (object) [ 852 'contextid' => $usercontext->id, 853 'defaultexpired' => 1, 854 'status' => expired_context::STATUS_APPROVED, 855 ]); 856 $expiredcontext->add_unexpiredroles([$role->id]); 857 $expiredcontext->save(); 858 859 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 860 ->setMethods([ 861 'delete_data_for_user', 862 'delete_data_for_users_in_context', 863 'delete_data_for_all_users_in_context', 864 ]) 865 ->getMock(); 866 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 867 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 868 $mockprivacymanager->expects($this->never())->method('delete_data_for_users_in_context'); 869 870 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 871 ->setMethods(['get_privacy_manager']) 872 ->getMock(); 873 874 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 875 $manager->set_progress(new \null_progress_trace()); 876 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 877 878 $this->assertEquals(0, $processedcourses); 879 $this->assertEquals(0, $processedusers); 880 881 $this->expectException('dml_missing_record_exception'); 882 $updatedcontext = new expired_context($expiredcontext->get('id')); 883 } 884 885 /** 886 * Ensure that a module context previously flagged as approved is removed with appropriate unexpiredroles kept. 887 */ 888 public function test_process_course_context_with_override_unexpired_role() { 889 global $DB; 890 $this->resetAfterTest(); 891 892 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 893 894 $role = $DB->get_record('role', ['shortname' => 'editingteacher']); 895 896 $override = new purpose_override(0, (object) [ 897 'purposeid' => $purposes->course->get('id'), 898 'roleid' => $role->id, 899 'retentionperiod' => 'P5Y', 900 ]); 901 $override->save(); 902 903 $course = $this->getDataGenerator()->create_course([ 904 'startdate' => time() - (2 * YEARSECS), 905 'enddate' => time() - YEARSECS, 906 ]); 907 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 908 $cm = get_coursemodule_from_instance('forum', $forum->id); 909 $forumcontext = \context_module::instance($cm->id); 910 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); 911 912 $student = $this->getDataGenerator()->create_user(); 913 $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); 914 $generator->create_discussion((object) [ 915 'course' => $forum->course, 916 'forum' => $forum->id, 917 'userid' => $student->id, 918 ]); 919 920 $teacher = $this->getDataGenerator()->create_user(); 921 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); 922 $generator->create_discussion((object) [ 923 'course' => $forum->course, 924 'forum' => $forum->id, 925 'userid' => $teacher->id, 926 ]); 927 928 // Create an existing expired_context. 929 $expiredcontext = new expired_context(0, (object) [ 930 'contextid' => $forumcontext->id, 931 'defaultexpired' => 1, 932 'status' => expired_context::STATUS_APPROVED, 933 ]); 934 $expiredcontext->add_unexpiredroles([$role->id]); 935 $expiredcontext->save(); 936 937 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 938 ->setMethods([ 939 'delete_data_for_user', 940 'delete_data_for_users_in_context', 941 'delete_data_for_all_users_in_context', 942 ]) 943 ->getMock(); 944 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 945 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 946 $mockprivacymanager 947 ->expects($this->once()) 948 ->method('delete_data_for_users_in_context') 949 ->with($this->callback(function($userlist) use ($student, $teacher) { 950 $forumlist = $userlist->get_userlist_for_component('mod_forum'); 951 $userids = $forumlist->get_userids(); 952 $this->assertCount(1, $userids); 953 $this->assertContains($student->id, $userids); 954 $this->assertNotContains($teacher->id, $userids); 955 return true; 956 })); 957 958 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 959 ->setMethods(['get_privacy_manager']) 960 ->getMock(); 961 962 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 963 $manager->set_progress(new \null_progress_trace()); 964 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 965 966 $this->assertEquals(1, $processedcourses); 967 $this->assertEquals(0, $processedusers); 968 969 $updatedcontext = new expired_context($expiredcontext->get('id')); 970 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 971 } 972 973 /** 974 * Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept. 975 */ 976 public function test_process_course_context_with_override_expired_role() { 977 global $DB; 978 $this->resetAfterTest(); 979 980 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y'); 981 982 $role = $DB->get_record('role', ['shortname' => 'student']); 983 984 $override = new purpose_override(0, (object) [ 985 'purposeid' => $purposes->course->get('id'), 986 'roleid' => $role->id, 987 'retentionperiod' => 'PT1M', 988 ]); 989 $override->save(); 990 991 $course = $this->getDataGenerator()->create_course([ 992 'startdate' => time() - (2 * YEARSECS), 993 'enddate' => time() - YEARSECS, 994 ]); 995 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 996 $cm = get_coursemodule_from_instance('forum', $forum->id); 997 $forumcontext = \context_module::instance($cm->id); 998 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); 999 1000 $student = $this->getDataGenerator()->create_user(); 1001 $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); 1002 $generator->create_discussion((object) [ 1003 'course' => $forum->course, 1004 'forum' => $forum->id, 1005 'userid' => $student->id, 1006 ]); 1007 1008 $teacher = $this->getDataGenerator()->create_user(); 1009 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); 1010 $generator->create_discussion((object) [ 1011 'course' => $forum->course, 1012 'forum' => $forum->id, 1013 'userid' => $teacher->id, 1014 ]); 1015 1016 // Create an existing expired_context. 1017 $expiredcontext = new expired_context(0, (object) [ 1018 'contextid' => $forumcontext->id, 1019 'defaultexpired' => 0, 1020 'status' => expired_context::STATUS_APPROVED, 1021 ]); 1022 $expiredcontext->add_expiredroles([$role->id]); 1023 $expiredcontext->save(); 1024 1025 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1026 ->setMethods([ 1027 'delete_data_for_user', 1028 'delete_data_for_users_in_context', 1029 'delete_data_for_all_users_in_context', 1030 ]) 1031 ->getMock(); 1032 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1033 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1034 $mockprivacymanager 1035 ->expects($this->once()) 1036 ->method('delete_data_for_users_in_context') 1037 ->with($this->callback(function($userlist) use ($student, $teacher) { 1038 $forumlist = $userlist->get_userlist_for_component('mod_forum'); 1039 $userids = $forumlist->get_userids(); 1040 $this->assertCount(1, $userids); 1041 $this->assertContains($student->id, $userids); 1042 $this->assertNotContains($teacher->id, $userids); 1043 return true; 1044 })); 1045 1046 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1047 ->setMethods(['get_privacy_manager']) 1048 ->getMock(); 1049 1050 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1051 $manager->set_progress(new \null_progress_trace()); 1052 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1053 1054 $this->assertEquals(1, $processedcourses); 1055 $this->assertEquals(0, $processedusers); 1056 1057 $updatedcontext = new expired_context($expiredcontext->get('id')); 1058 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1059 } 1060 1061 /** 1062 * Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept. 1063 */ 1064 public function test_process_course_context_with_user_in_both_lists() { 1065 global $DB; 1066 $this->resetAfterTest(); 1067 1068 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y'); 1069 1070 $role = $DB->get_record('role', ['shortname' => 'student']); 1071 1072 $override = new purpose_override(0, (object) [ 1073 'purposeid' => $purposes->course->get('id'), 1074 'roleid' => $role->id, 1075 'retentionperiod' => 'PT1M', 1076 ]); 1077 $override->save(); 1078 1079 $course = $this->getDataGenerator()->create_course([ 1080 'startdate' => time() - (2 * YEARSECS), 1081 'enddate' => time() - YEARSECS, 1082 ]); 1083 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1084 $cm = get_coursemodule_from_instance('forum', $forum->id); 1085 $forumcontext = \context_module::instance($cm->id); 1086 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); 1087 1088 $teacher = $this->getDataGenerator()->create_user(); 1089 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); 1090 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'student'); 1091 $generator->create_discussion((object) [ 1092 'course' => $forum->course, 1093 'forum' => $forum->id, 1094 'userid' => $teacher->id, 1095 ]); 1096 1097 $student = $this->getDataGenerator()->create_user(); 1098 $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); 1099 $generator->create_discussion((object) [ 1100 'course' => $forum->course, 1101 'forum' => $forum->id, 1102 'userid' => $student->id, 1103 ]); 1104 1105 // Create an existing expired_context. 1106 $expiredcontext = new expired_context(0, (object) [ 1107 'contextid' => $forumcontext->id, 1108 'defaultexpired' => 0, 1109 'status' => expired_context::STATUS_APPROVED, 1110 ]); 1111 $expiredcontext->add_expiredroles([$role->id]); 1112 $expiredcontext->save(); 1113 1114 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1115 ->setMethods([ 1116 'delete_data_for_user', 1117 'delete_data_for_users_in_context', 1118 'delete_data_for_all_users_in_context', 1119 ]) 1120 ->getMock(); 1121 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1122 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1123 $mockprivacymanager 1124 ->expects($this->once()) 1125 ->method('delete_data_for_users_in_context') 1126 ->with($this->callback(function($userlist) use ($student, $teacher) { 1127 $forumlist = $userlist->get_userlist_for_component('mod_forum'); 1128 $userids = $forumlist->get_userids(); 1129 $this->assertCount(1, $userids); 1130 $this->assertContains($student->id, $userids); 1131 $this->assertNotContains($teacher->id, $userids); 1132 return true; 1133 })); 1134 1135 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1136 ->setMethods(['get_privacy_manager']) 1137 ->getMock(); 1138 1139 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1140 $manager->set_progress(new \null_progress_trace()); 1141 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1142 1143 $this->assertEquals(1, $processedcourses); 1144 $this->assertEquals(0, $processedusers); 1145 1146 $updatedcontext = new expired_context($expiredcontext->get('id')); 1147 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1148 } 1149 1150 /** 1151 * Ensure that a module context previously flagged as approved is removed with appropriate expiredroles kept. 1152 */ 1153 public function test_process_course_context_with_user_in_both_lists_expired() { 1154 global $DB; 1155 $this->resetAfterTest(); 1156 1157 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P5Y'); 1158 1159 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 1160 $override = new purpose_override(0, (object) [ 1161 'purposeid' => $purposes->course->get('id'), 1162 'roleid' => $studentrole->id, 1163 'retentionperiod' => 'PT1M', 1164 ]); 1165 $override->save(); 1166 1167 $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); 1168 $override = new purpose_override(0, (object) [ 1169 'purposeid' => $purposes->course->get('id'), 1170 'roleid' => $teacherrole->id, 1171 'retentionperiod' => 'PT1M', 1172 ]); 1173 $override->save(); 1174 1175 $course = $this->getDataGenerator()->create_course([ 1176 'startdate' => time() - (2 * YEARSECS), 1177 'enddate' => time() - YEARSECS, 1178 ]); 1179 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1180 $cm = get_coursemodule_from_instance('forum', $forum->id); 1181 $forumcontext = \context_module::instance($cm->id); 1182 $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum'); 1183 1184 $teacher = $this->getDataGenerator()->create_user(); 1185 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); 1186 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'student'); 1187 $generator->create_discussion((object) [ 1188 'course' => $forum->course, 1189 'forum' => $forum->id, 1190 'userid' => $teacher->id, 1191 ]); 1192 1193 $student = $this->getDataGenerator()->create_user(); 1194 $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); 1195 $generator->create_discussion((object) [ 1196 'course' => $forum->course, 1197 'forum' => $forum->id, 1198 'userid' => $student->id, 1199 ]); 1200 1201 // Create an existing expired_context. 1202 $expiredcontext = new expired_context(0, (object) [ 1203 'contextid' => $forumcontext->id, 1204 'defaultexpired' => 0, 1205 'status' => expired_context::STATUS_APPROVED, 1206 ]); 1207 $expiredcontext->add_expiredroles([$studentrole->id, $teacherrole->id]); 1208 $expiredcontext->save(); 1209 1210 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1211 ->setMethods([ 1212 'delete_data_for_user', 1213 'delete_data_for_users_in_context', 1214 'delete_data_for_all_users_in_context', 1215 ]) 1216 ->getMock(); 1217 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1218 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1219 $mockprivacymanager 1220 ->expects($this->once()) 1221 ->method('delete_data_for_users_in_context') 1222 ->with($this->callback(function($userlist) use ($student, $teacher) { 1223 $forumlist = $userlist->get_userlist_for_component('mod_forum'); 1224 $userids = $forumlist->get_userids(); 1225 $this->assertCount(2, $userids); 1226 $this->assertContains($student->id, $userids); 1227 $this->assertContains($teacher->id, $userids); 1228 return true; 1229 })); 1230 1231 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1232 ->setMethods(['get_privacy_manager']) 1233 ->getMock(); 1234 1235 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1236 $manager->set_progress(new \null_progress_trace()); 1237 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1238 1239 $this->assertEquals(1, $processedcourses); 1240 $this->assertEquals(0, $processedusers); 1241 1242 $updatedcontext = new expired_context($expiredcontext->get('id')); 1243 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1244 } 1245 1246 /** 1247 * Ensure that a site not setup will not process anything. 1248 */ 1249 public function test_process_not_setup() { 1250 $this->resetAfterTest(); 1251 1252 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 1253 $usercontext = \context_user::instance($user->id); 1254 1255 // Create an existing expired_context. 1256 $expiredcontext = new expired_context(0, (object) [ 1257 'contextid' => $usercontext->id, 1258 'status' => expired_context::STATUS_EXPIRED, 1259 ]); 1260 $expiredcontext->save(); 1261 1262 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1263 ->setMethods([ 1264 'delete_data_for_user', 1265 'delete_data_for_all_users_in_context', 1266 ]) 1267 ->getMock(); 1268 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1269 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1270 1271 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1272 ->setMethods(['get_privacy_manager']) 1273 ->getMock(); 1274 $manager->set_progress(new \null_progress_trace()); 1275 1276 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1277 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1278 1279 $this->assertEquals(0, $processedcourses); 1280 $this->assertEquals(0, $processedusers); 1281 } 1282 1283 /** 1284 * Ensure that a user with no lastaccess is not flagged for deletion. 1285 */ 1286 public function test_process_none_approved() { 1287 $this->resetAfterTest(); 1288 1289 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1290 1291 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 1292 $usercontext = \context_user::instance($user->id); 1293 1294 // Create an existing expired_context. 1295 $expiredcontext = new expired_context(0, (object) [ 1296 'contextid' => $usercontext->id, 1297 'status' => expired_context::STATUS_EXPIRED, 1298 ]); 1299 $expiredcontext->save(); 1300 1301 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1302 ->setMethods([ 1303 'delete_data_for_user', 1304 'delete_data_for_all_users_in_context', 1305 ]) 1306 ->getMock(); 1307 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1308 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1309 1310 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1311 ->setMethods(['get_privacy_manager']) 1312 ->getMock(); 1313 $manager->set_progress(new \null_progress_trace()); 1314 1315 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1316 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1317 1318 $this->assertEquals(0, $processedcourses); 1319 $this->assertEquals(0, $processedusers); 1320 } 1321 1322 /** 1323 * Ensure that a user with no lastaccess is not flagged for deletion. 1324 */ 1325 public function test_process_no_context() { 1326 $this->resetAfterTest(); 1327 1328 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1329 1330 // Create an existing expired_context. 1331 $expiredcontext = new expired_context(0, (object) [ 1332 'contextid' => -1, 1333 'status' => expired_context::STATUS_APPROVED, 1334 ]); 1335 $expiredcontext->save(); 1336 1337 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1338 ->setMethods([ 1339 'delete_data_for_user', 1340 'delete_data_for_all_users_in_context', 1341 ]) 1342 ->getMock(); 1343 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1344 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1345 1346 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1347 ->setMethods(['get_privacy_manager']) 1348 ->getMock(); 1349 $manager->set_progress(new \null_progress_trace()); 1350 1351 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1352 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1353 1354 $this->assertEquals(0, $processedcourses); 1355 $this->assertEquals(0, $processedusers); 1356 1357 $this->expectException('dml_missing_record_exception'); 1358 new expired_context($expiredcontext->get('id')); 1359 } 1360 1361 /** 1362 * Ensure that a user context previously flagged as approved is removed. 1363 */ 1364 public function test_process_user_context() { 1365 $this->resetAfterTest(); 1366 1367 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 1368 1369 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 1370 $usercontext = \context_user::instance($user->id); 1371 1372 $this->setUser($user); 1373 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 1374 $blockcontext = \context_block::instance($block->instance->id); 1375 $this->setUser(); 1376 1377 // Create an existing expired_context. 1378 $expiredcontext = new expired_context(0, (object) [ 1379 'contextid' => $usercontext->id, 1380 'status' => expired_context::STATUS_APPROVED, 1381 ]); 1382 $expiredcontext->save(); 1383 1384 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1385 ->setMethods([ 1386 'delete_data_for_user', 1387 'delete_data_for_all_users_in_context', 1388 ]) 1389 ->getMock(); 1390 $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user'); 1391 $mockprivacymanager->expects($this->exactly(2)) 1392 ->method('delete_data_for_all_users_in_context') 1393 ->withConsecutive( 1394 [$blockcontext], 1395 [$usercontext] 1396 ); 1397 1398 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1399 ->setMethods(['get_privacy_manager']) 1400 ->getMock(); 1401 $manager->set_progress(new \null_progress_trace()); 1402 1403 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1404 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1405 1406 $this->assertEquals(0, $processedcourses); 1407 $this->assertEquals(1, $processedusers); 1408 1409 $updatedcontext = new expired_context($expiredcontext->get('id')); 1410 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1411 1412 // Flag all expired contexts again. 1413 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 1414 1415 $this->assertEquals(0, $flaggedcourses); 1416 $this->assertEquals(0, $flaggedusers); 1417 1418 // Ensure that the deleted context record is still present. 1419 $updatedcontext = new expired_context($expiredcontext->get('id')); 1420 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1421 } 1422 1423 /** 1424 * Ensure that a course context previously flagged as approved is removed. 1425 */ 1426 public function test_process_course_context() { 1427 $this->resetAfterTest(); 1428 1429 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1430 1431 $course = $this->getDataGenerator()->create_course([ 1432 'startdate' => time() - (2 * YEARSECS), 1433 'enddate' => time() - YEARSECS, 1434 ]); 1435 $coursecontext = \context_course::instance($course->id); 1436 1437 // Create an existing expired_context. 1438 $expiredcontext = new expired_context(0, (object) [ 1439 'contextid' => $coursecontext->id, 1440 'status' => expired_context::STATUS_APPROVED, 1441 ]); 1442 $expiredcontext->save(); 1443 1444 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1445 ->setMethods([ 1446 'delete_data_for_user', 1447 'delete_data_for_all_users_in_context', 1448 ]) 1449 ->getMock(); 1450 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1451 $mockprivacymanager->expects($this->once())->method('delete_data_for_all_users_in_context'); 1452 1453 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1454 ->setMethods(['get_privacy_manager']) 1455 ->getMock(); 1456 $manager->set_progress(new \null_progress_trace()); 1457 1458 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1459 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1460 1461 $this->assertEquals(1, $processedcourses); 1462 $this->assertEquals(0, $processedusers); 1463 1464 $updatedcontext = new expired_context($expiredcontext->get('id')); 1465 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1466 } 1467 1468 /** 1469 * Ensure that a user context previously flagged as approved is not removed if the user then logs in. 1470 */ 1471 public function test_process_user_context_logged_in_after_approval() { 1472 $this->resetAfterTest(); 1473 1474 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 1475 1476 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 1477 $usercontext = \context_user::instance($user->id); 1478 1479 $this->setUser($user); 1480 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 1481 $context = \context_block::instance($block->instance->id); 1482 $this->setUser(); 1483 1484 // Create an existing expired_context. 1485 $expiredcontext = new expired_context(0, (object) [ 1486 'contextid' => $usercontext->id, 1487 'status' => expired_context::STATUS_APPROVED, 1488 ]); 1489 $expiredcontext->save(); 1490 1491 // Now bump the user's last login time. 1492 $this->setUser($user); 1493 user_accesstime_log(); 1494 $this->setUser(); 1495 1496 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1497 ->setMethods([ 1498 'delete_data_for_user', 1499 'delete_data_for_all_users_in_context', 1500 ]) 1501 ->getMock(); 1502 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1503 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1504 1505 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1506 ->setMethods(['get_privacy_manager']) 1507 ->getMock(); 1508 $manager->set_progress(new \null_progress_trace()); 1509 1510 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1511 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1512 1513 $this->assertEquals(0, $processedcourses); 1514 $this->assertEquals(0, $processedusers); 1515 1516 $this->expectException('dml_missing_record_exception'); 1517 new expired_context($expiredcontext->get('id')); 1518 } 1519 1520 /** 1521 * Ensure that a user context previously flagged as approved is not removed if the purpose has changed. 1522 */ 1523 public function test_process_user_context_changed_after_approved() { 1524 $this->resetAfterTest(); 1525 1526 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 1527 1528 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 1529 $usercontext = \context_user::instance($user->id); 1530 1531 $this->setUser($user); 1532 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 1533 $context = \context_block::instance($block->instance->id); 1534 $this->setUser(); 1535 1536 // Create an existing expired_context. 1537 $expiredcontext = new expired_context(0, (object) [ 1538 'contextid' => $usercontext->id, 1539 'status' => expired_context::STATUS_APPROVED, 1540 ]); 1541 $expiredcontext->save(); 1542 1543 // Now make the user a site admin. 1544 $admins = explode(',', get_config('moodle', 'siteadmins')); 1545 $admins[] = $user->id; 1546 set_config('siteadmins', implode(',', $admins)); 1547 1548 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1549 ->setMethods([ 1550 'delete_data_for_user', 1551 'delete_data_for_all_users_in_context', 1552 ]) 1553 ->getMock(); 1554 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1555 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1556 1557 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1558 ->setMethods(['get_privacy_manager']) 1559 ->getMock(); 1560 $manager->set_progress(new \null_progress_trace()); 1561 1562 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1563 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1564 1565 $this->assertEquals(0, $processedcourses); 1566 $this->assertEquals(0, $processedusers); 1567 1568 $this->expectException('dml_missing_record_exception'); 1569 new expired_context($expiredcontext->get('id')); 1570 } 1571 1572 /** 1573 * Ensure that a user with a historically expired expired block record child is cleaned up. 1574 */ 1575 public function test_process_user_historic_block_unapproved() { 1576 $this->resetAfterTest(); 1577 1578 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 1579 1580 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]); 1581 $usercontext = \context_user::instance($user->id); 1582 1583 $this->setUser($user); 1584 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 1585 $blockcontext = \context_block::instance($block->instance->id); 1586 $this->setUser(); 1587 1588 // Create an expired_context for the user. 1589 $expiredusercontext = new expired_context(0, (object) [ 1590 'contextid' => $usercontext->id, 1591 'status' => expired_context::STATUS_APPROVED, 1592 ]); 1593 $expiredusercontext->save(); 1594 1595 // Create an existing expired_context which has not been approved for the block. 1596 $expiredblockcontext = new expired_context(0, (object) [ 1597 'contextid' => $blockcontext->id, 1598 'status' => expired_context::STATUS_EXPIRED, 1599 ]); 1600 $expiredblockcontext->save(); 1601 1602 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1603 ->setMethods([ 1604 'delete_data_for_user', 1605 'delete_data_for_all_users_in_context', 1606 ]) 1607 ->getMock(); 1608 $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user'); 1609 $mockprivacymanager->expects($this->exactly(2)) 1610 ->method('delete_data_for_all_users_in_context') 1611 ->withConsecutive( 1612 [$blockcontext], 1613 [$usercontext] 1614 ); 1615 1616 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1617 ->setMethods(['get_privacy_manager']) 1618 ->getMock(); 1619 $manager->set_progress(new \null_progress_trace()); 1620 1621 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1622 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1623 1624 $this->assertEquals(0, $processedcourses); 1625 $this->assertEquals(1, $processedusers); 1626 1627 $updatedcontext = new expired_context($expiredusercontext->get('id')); 1628 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1629 } 1630 1631 /** 1632 * Ensure that a user with a block which has a default retention period which has not expired, is still expired. 1633 */ 1634 public function test_process_user_historic_unexpired_child() { 1635 $this->resetAfterTest(); 1636 1637 $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 1638 $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK); 1639 1640 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]); 1641 $usercontext = \context_user::instance($user->id); 1642 1643 $this->setUser($user); 1644 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 1645 $blockcontext = \context_block::instance($block->instance->id); 1646 $this->setUser(); 1647 1648 // Create an expired_context for the user. 1649 $expiredusercontext = new expired_context(0, (object) [ 1650 'contextid' => $usercontext->id, 1651 'status' => expired_context::STATUS_APPROVED, 1652 ]); 1653 $expiredusercontext->save(); 1654 1655 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1656 ->setMethods([ 1657 'delete_data_for_user', 1658 'delete_data_for_all_users_in_context', 1659 ]) 1660 ->getMock(); 1661 $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user'); 1662 $mockprivacymanager->expects($this->exactly(2)) 1663 ->method('delete_data_for_all_users_in_context') 1664 ->withConsecutive( 1665 [$blockcontext], 1666 [$usercontext] 1667 ); 1668 1669 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1670 ->setMethods(['get_privacy_manager']) 1671 ->getMock(); 1672 $manager->set_progress(new \null_progress_trace()); 1673 1674 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1675 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1676 1677 $this->assertEquals(0, $processedcourses); 1678 $this->assertEquals(1, $processedusers); 1679 1680 $updatedcontext = new expired_context($expiredusercontext->get('id')); 1681 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1682 } 1683 1684 /** 1685 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is 1686 * updated. 1687 */ 1688 public function test_process_course_context_updated() { 1689 $this->resetAfterTest(); 1690 1691 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1692 1693 $course = $this->getDataGenerator()->create_course([ 1694 'startdate' => time() - (2 * YEARSECS), 1695 'enddate' => time() - YEARSECS, 1696 ]); 1697 $coursecontext = \context_course::instance($course->id); 1698 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1699 1700 // Create an existing expired_context. 1701 $expiredcontext = new expired_context(0, (object) [ 1702 'contextid' => $coursecontext->id, 1703 'status' => expired_context::STATUS_APPROVED, 1704 ]); 1705 $expiredcontext->save(); 1706 1707 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1708 ->setMethods([ 1709 'delete_data_for_user', 1710 'delete_data_for_all_users_in_context', 1711 ]) 1712 ->getMock(); 1713 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1714 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1715 1716 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1717 ->setMethods(['get_privacy_manager']) 1718 ->getMock(); 1719 $manager->set_progress(new \null_progress_trace()); 1720 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1721 1722 // Changing the retention period to a longer period will remove the expired_context record. 1723 $purposes->activity->set('retentionperiod', 'P5Y'); 1724 $purposes->activity->save(); 1725 1726 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1727 1728 $this->assertEquals(0, $processedcourses); 1729 $this->assertEquals(0, $processedusers); 1730 1731 $this->expectException('dml_missing_record_exception'); 1732 $updatedcontext = new expired_context($expiredcontext->get('id')); 1733 } 1734 1735 /** 1736 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is 1737 * updated. 1738 */ 1739 public function test_process_course_context_outstanding_children() { 1740 $this->resetAfterTest(); 1741 1742 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1743 1744 $course = $this->getDataGenerator()->create_course([ 1745 'startdate' => time() - (2 * YEARSECS), 1746 'enddate' => time() - YEARSECS, 1747 ]); 1748 $coursecontext = \context_course::instance($course->id); 1749 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1750 1751 // Create an existing expired_context. 1752 $expiredcontext = new expired_context(0, (object) [ 1753 'contextid' => $coursecontext->id, 1754 'status' => expired_context::STATUS_APPROVED, 1755 ]); 1756 $expiredcontext->save(); 1757 1758 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1759 ->setMethods([ 1760 'delete_data_for_user', 1761 'delete_data_for_all_users_in_context', 1762 ]) 1763 ->getMock(); 1764 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1765 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1766 1767 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1768 ->setMethods(['get_privacy_manager']) 1769 ->getMock(); 1770 $manager->set_progress(new \null_progress_trace()); 1771 1772 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1773 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1774 1775 $this->assertEquals(0, $processedcourses); 1776 $this->assertEquals(0, $processedusers); 1777 1778 $updatedcontext = new expired_context($expiredcontext->get('id')); 1779 1780 // No change - we just can't process it until the children have finished. 1781 $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status')); 1782 } 1783 1784 /** 1785 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is 1786 * updated. 1787 */ 1788 public function test_process_course_context_pending_children() { 1789 $this->resetAfterTest(); 1790 1791 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1792 1793 $course = $this->getDataGenerator()->create_course([ 1794 'startdate' => time() - (2 * YEARSECS), 1795 'enddate' => time() - YEARSECS, 1796 ]); 1797 $coursecontext = \context_course::instance($course->id); 1798 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1799 $cm = get_coursemodule_from_instance('forum', $forum->id); 1800 $forumcontext = \context_module::instance($cm->id); 1801 1802 // Create an existing expired_context for the course. 1803 $expiredcoursecontext = new expired_context(0, (object) [ 1804 'contextid' => $coursecontext->id, 1805 'status' => expired_context::STATUS_APPROVED, 1806 ]); 1807 $expiredcoursecontext->save(); 1808 1809 // And for the forum. 1810 $expiredforumcontext = new expired_context(0, (object) [ 1811 'contextid' => $forumcontext->id, 1812 'status' => expired_context::STATUS_EXPIRED, 1813 ]); 1814 $expiredforumcontext->save(); 1815 1816 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1817 ->setMethods([ 1818 'delete_data_for_user', 1819 'delete_data_for_all_users_in_context', 1820 ]) 1821 ->getMock(); 1822 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1823 $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context'); 1824 1825 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1826 ->setMethods(['get_privacy_manager']) 1827 ->getMock(); 1828 $manager->set_progress(new \null_progress_trace()); 1829 1830 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1831 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1832 1833 $this->assertEquals(0, $processedcourses); 1834 $this->assertEquals(0, $processedusers); 1835 1836 $updatedcontext = new expired_context($expiredcoursecontext->get('id')); 1837 1838 // No change - we just can't process it until the children have finished. 1839 $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status')); 1840 } 1841 1842 /** 1843 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is 1844 * updated. 1845 */ 1846 public function test_process_course_context_approved_children() { 1847 $this->resetAfterTest(); 1848 1849 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 1850 1851 $course = $this->getDataGenerator()->create_course([ 1852 'startdate' => time() - (2 * YEARSECS), 1853 'enddate' => time() - YEARSECS, 1854 ]); 1855 $coursecontext = \context_course::instance($course->id); 1856 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1857 $cm = get_coursemodule_from_instance('forum', $forum->id); 1858 $forumcontext = \context_module::instance($cm->id); 1859 1860 // Create an existing expired_context for the course. 1861 $expiredcoursecontext = new expired_context(0, (object) [ 1862 'contextid' => $coursecontext->id, 1863 'status' => expired_context::STATUS_APPROVED, 1864 ]); 1865 $expiredcoursecontext->save(); 1866 1867 // And for the forum. 1868 $expiredforumcontext = new expired_context(0, (object) [ 1869 'contextid' => $forumcontext->id, 1870 'status' => expired_context::STATUS_APPROVED, 1871 ]); 1872 $expiredforumcontext->save(); 1873 1874 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class) 1875 ->setMethods([ 1876 'delete_data_for_user', 1877 'delete_data_for_all_users_in_context', 1878 ]) 1879 ->getMock(); 1880 $mockprivacymanager->expects($this->never())->method('delete_data_for_user'); 1881 $mockprivacymanager->expects($this->exactly(2)) 1882 ->method('delete_data_for_all_users_in_context') 1883 ->withConsecutive( 1884 [$forumcontext], 1885 [$coursecontext] 1886 ); 1887 1888 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class) 1889 ->setMethods(['get_privacy_manager']) 1890 ->getMock(); 1891 $manager->set_progress(new \null_progress_trace()); 1892 1893 $manager->method('get_privacy_manager')->willReturn($mockprivacymanager); 1894 1895 // Initially only the forum will be processed. 1896 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1897 1898 $this->assertEquals(1, $processedcourses); 1899 $this->assertEquals(0, $processedusers); 1900 1901 $updatedcontext = new expired_context($expiredforumcontext->get('id')); 1902 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1903 1904 // The course won't have been processed yet. 1905 $updatedcontext = new expired_context($expiredcoursecontext->get('id')); 1906 $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status')); 1907 1908 // A subsequent run will cause the course to processed as it is no longer dependent upon the child contexts. 1909 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 1910 1911 $this->assertEquals(1, $processedcourses); 1912 $this->assertEquals(0, $processedusers); 1913 $updatedcontext = new expired_context($expiredcoursecontext->get('id')); 1914 $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status')); 1915 } 1916 1917 /** 1918 * Test that the can_process_deletion function returns expected results. 1919 * 1920 * @dataProvider can_process_deletion_provider 1921 * @param int $status 1922 * @param bool $expected 1923 */ 1924 public function test_can_process_deletion($status, $expected) { 1925 $purpose = new expired_context(0, (object) [ 1926 'status' => $status, 1927 1928 'contextid' => \context_system::instance()->id, 1929 ]); 1930 1931 $this->assertEquals($expected, $purpose->can_process_deletion()); 1932 } 1933 1934 /** 1935 * Data provider for the can_process_deletion tests. 1936 * 1937 * @return array 1938 */ 1939 public function can_process_deletion_provider() : array { 1940 return [ 1941 'Pending' => [ 1942 expired_context::STATUS_EXPIRED, 1943 false, 1944 ], 1945 'Approved' => [ 1946 expired_context::STATUS_APPROVED, 1947 true, 1948 ], 1949 'Complete' => [ 1950 expired_context::STATUS_CLEANED, 1951 false, 1952 ], 1953 ]; 1954 } 1955 1956 /** 1957 * Test that the is_complete function returns expected results. 1958 * 1959 * @dataProvider is_complete_provider 1960 * @param int $status 1961 * @param bool $expected 1962 */ 1963 public function test_is_complete($status, $expected) { 1964 $purpose = new expired_context(0, (object) [ 1965 'status' => $status, 1966 'contextid' => \context_system::instance()->id, 1967 ]); 1968 1969 $this->assertEquals($expected, $purpose->is_complete()); 1970 } 1971 1972 /** 1973 * Data provider for the is_complete tests. 1974 * 1975 * @return array 1976 */ 1977 public function is_complete_provider() : array { 1978 return [ 1979 'Pending' => [ 1980 expired_context::STATUS_EXPIRED, 1981 false, 1982 ], 1983 'Approved' => [ 1984 expired_context::STATUS_APPROVED, 1985 false, 1986 ], 1987 'Complete' => [ 1988 expired_context::STATUS_CLEANED, 1989 true, 1990 ], 1991 ]; 1992 } 1993 1994 /** 1995 * Test that the is_fully_expired function returns expected results. 1996 * 1997 * @dataProvider is_fully_expired_provider 1998 * @param array $record 1999 * @param bool $expected 2000 */ 2001 public function test_is_fully_expired($record, $expected) { 2002 $purpose = new expired_context(0, (object) $record); 2003 2004 $this->assertEquals($expected, $purpose->is_fully_expired()); 2005 } 2006 2007 /** 2008 * Data provider for the is_fully_expired tests. 2009 * 2010 * @return array 2011 */ 2012 public function is_fully_expired_provider() : array { 2013 return [ 2014 'Fully expired' => [ 2015 [ 2016 'status' => expired_context::STATUS_APPROVED, 2017 'defaultexpired' => 1, 2018 ], 2019 true, 2020 ], 2021 'Unexpired roles present' => [ 2022 [ 2023 'status' => expired_context::STATUS_APPROVED, 2024 'defaultexpired' => 1, 2025 'unexpiredroles' => json_encode([1]), 2026 ], 2027 false, 2028 ], 2029 'Only some expired roles present' => [ 2030 [ 2031 'status' => expired_context::STATUS_APPROVED, 2032 'defaultexpired' => 0, 2033 'expiredroles' => json_encode([1]), 2034 ], 2035 false, 2036 ], 2037 ]; 2038 } 2039 2040 /** 2041 * Ensure that any orphaned records are removed once the context has been removed. 2042 */ 2043 public function test_orphaned_records_are_cleared() { 2044 $this->resetAfterTest(); 2045 2046 $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H'); 2047 2048 $course = $this->getDataGenerator()->create_course([ 2049 'startdate' => time() - (2 * YEARSECS), 2050 'enddate' => time() - YEARSECS, 2051 ]); 2052 $context = \context_course::instance($course->id); 2053 2054 // Flag all expired contexts. 2055 $manager = new \tool_dataprivacy\expired_contexts_manager(); 2056 $manager->set_progress(new \null_progress_trace()); 2057 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 2058 2059 $this->assertEquals(1, $flaggedcourses); 2060 $this->assertEquals(0, $flaggedusers); 2061 2062 // Ensure that the record currently exists. 2063 $expiredcontext = expired_context::get_record(['contextid' => $context->id]); 2064 $this->assertNotFalse($expiredcontext); 2065 2066 // Approve it. 2067 $expiredcontext->set('status', expired_context::STATUS_APPROVED)->save(); 2068 2069 // Process deletions. 2070 list($processedcourses, $processedusers) = $manager->process_approved_deletions(); 2071 2072 $this->assertEquals(1, $processedcourses); 2073 $this->assertEquals(0, $processedusers); 2074 2075 // Ensure that the record still exists. 2076 $expiredcontext = expired_context::get_record(['contextid' => $context->id]); 2077 $this->assertNotFalse($expiredcontext); 2078 2079 // Remove the actual course. 2080 delete_course($course->id, false); 2081 2082 // The record will still exist until we flag it again. 2083 $expiredcontext = expired_context::get_record(['contextid' => $context->id]); 2084 $this->assertNotFalse($expiredcontext); 2085 2086 list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts(); 2087 $expiredcontext = expired_context::get_record(['contextid' => $context->id]); 2088 $this->assertFalse($expiredcontext); 2089 } 2090 2091 /** 2092 * Ensure that the progres tracer works as expected out of the box. 2093 */ 2094 public function test_progress_tracer_default() { 2095 $manager = new \tool_dataprivacy\expired_contexts_manager(); 2096 2097 $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class); 2098 $rcm = $rc->getMethod('get_progress'); 2099 2100 $rcm->setAccessible(true); 2101 $this->assertInstanceOf(\text_progress_trace::class, $rcm->invoke($manager)); 2102 } 2103 2104 /** 2105 * Ensure that the progres tracer works as expected when given a specific traer. 2106 */ 2107 public function test_progress_tracer_set() { 2108 $manager = new \tool_dataprivacy\expired_contexts_manager(); 2109 $mytrace = new \null_progress_trace(); 2110 $manager->set_progress($mytrace); 2111 2112 $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class); 2113 $rcm = $rc->getMethod('get_progress'); 2114 2115 $rcm->setAccessible(true); 2116 $this->assertSame($mytrace, $rcm->invoke($manager)); 2117 } 2118 2119 /** 2120 * Creates an HTML block on a user. 2121 * 2122 * @param string $title 2123 * @param string $body 2124 * @param string $format 2125 * @return \block_instance 2126 */ 2127 protected function create_user_block($title, $body, $format) { 2128 global $USER; 2129 2130 $configdata = (object) [ 2131 'title' => $title, 2132 'text' => [ 2133 'itemid' => 19, 2134 'text' => $body, 2135 'format' => $format, 2136 ], 2137 ]; 2138 2139 $this->create_block($this->construct_user_page($USER)); 2140 $block = $this->get_last_block_on_page($this->construct_user_page($USER)); 2141 $block = block_instance('html', $block->instance); 2142 $block->instance_config_save((object) $configdata); 2143 2144 return $block; 2145 } 2146 2147 /** 2148 * Creates an HTML block on a page. 2149 * 2150 * @param \page $page Page 2151 */ 2152 protected function create_block($page) { 2153 $page->blocks->add_block_at_end_of_default_region('html'); 2154 } 2155 2156 /** 2157 * Constructs a Page object for the User Dashboard. 2158 * 2159 * @param \stdClass $user User to create Dashboard for. 2160 * @return \moodle_page 2161 */ 2162 protected function construct_user_page(\stdClass $user) { 2163 $page = new \moodle_page(); 2164 $page->set_context(\context_user::instance($user->id)); 2165 $page->set_pagelayout('mydashboard'); 2166 $page->set_pagetype('my-index'); 2167 $page->blocks->load_blocks(); 2168 return $page; 2169 } 2170 2171 /** 2172 * Get the last block on the page. 2173 * 2174 * @param \page $page Page 2175 * @return \block_html Block instance object 2176 */ 2177 protected function get_last_block_on_page($page) { 2178 $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region()); 2179 $block = end($blocks); 2180 2181 return $block; 2182 } 2183 2184 /** 2185 * Test the is_context_expired functions when supplied with the system context. 2186 */ 2187 public function test_is_context_expired_system() { 2188 $this->resetAfterTest(); 2189 $this->setup_basics('PT1H', 'PT1H', 'P1D'); 2190 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2191 2192 $this->assertFalse(expired_contexts_manager::is_context_expired(\context_system::instance())); 2193 $this->assertFalse( 2194 expired_contexts_manager::is_context_expired_or_unprotected_for_user(\context_system::instance(), $user)); 2195 } 2196 2197 /** 2198 * Test the is_context_expired functions when supplied with a block in the user context. 2199 * 2200 * Children of a user context always follow the user expiry rather than any context level defaults (e.g. at the 2201 * block level. 2202 */ 2203 public function test_is_context_expired_user_block() { 2204 $this->resetAfterTest(); 2205 2206 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D'); 2207 $purposes->block = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK); 2208 2209 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2210 $this->setUser($user); 2211 $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN); 2212 $blockcontext = \context_block::instance($block->instance->id); 2213 $this->setUser(); 2214 2215 // Protected flags have no bearing on expiry of user subcontexts. 2216 $this->assertTrue(expired_contexts_manager::is_context_expired($blockcontext)); 2217 2218 $purposes->block->set('protected', 1)->save(); 2219 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user)); 2220 2221 $purposes->block->set('protected', 0)->save(); 2222 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user)); 2223 } 2224 2225 /** 2226 * Test the is_context_expired functions when supplied with the front page course. 2227 */ 2228 public function test_is_context_expired_frontpage() { 2229 $this->resetAfterTest(); 2230 2231 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D'); 2232 2233 $frontcourse = get_site(); 2234 $frontcoursecontext = \context_course::instance($frontcourse->id); 2235 2236 $sitenews = $this->getDataGenerator()->create_module('forum', ['course' => $frontcourse->id]); 2237 $cm = get_coursemodule_from_instance('forum', $sitenews->id); 2238 $sitenewscontext = \context_module::instance($cm->id); 2239 2240 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2241 2242 $this->assertFalse(expired_contexts_manager::is_context_expired($frontcoursecontext)); 2243 $this->assertFalse(expired_contexts_manager::is_context_expired($sitenewscontext)); 2244 2245 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user)); 2246 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user)); 2247 2248 // Protecting the course contextlevel does not impact the front page. 2249 $purposes->course->set('protected', 1)->save(); 2250 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user)); 2251 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user)); 2252 2253 // Protecting the system contextlevel affects the front page, too. 2254 $purposes->system->set('protected', 1)->save(); 2255 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user)); 2256 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user)); 2257 } 2258 2259 /** 2260 * Test the is_context_expired functions when supplied with an expired course. 2261 */ 2262 public function test_is_context_expired_course_expired() { 2263 $this->resetAfterTest(); 2264 2265 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D'); 2266 2267 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2268 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]); 2269 $coursecontext = \context_course::instance($course->id); 2270 2271 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2272 2273 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2274 2275 $purposes->course->set('protected', 1)->save(); 2276 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2277 2278 $purposes->course->set('protected', 0)->save(); 2279 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2280 } 2281 2282 /** 2283 * Test the is_context_expired functions when supplied with an unexpired course. 2284 */ 2285 public function test_is_context_expired_course_unexpired() { 2286 $this->resetAfterTest(); 2287 2288 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D'); 2289 2290 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2291 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2292 $coursecontext = \context_course::instance($course->id); 2293 2294 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2295 2296 $this->assertTrue(expired_contexts_manager::is_context_expired($coursecontext)); 2297 2298 $purposes->course->set('protected', 1)->save(); 2299 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2300 2301 $purposes->course->set('protected', 0)->save(); 2302 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2303 } 2304 2305 /** 2306 * Test the is_context_expired functions when supplied with an unexpired course and a child context in the course which is protected. 2307 * 2308 * When a child context has a specific purpose set, then that purpose should be respected with respect to the 2309 * course. 2310 * 2311 * If the course is still within the expiry period for the child context, then that child's protected flag should be 2312 * respected, even when the course may have expired. 2313 */ 2314 public function test_is_child_context_expired_course_unexpired_with_child() { 2315 $this->resetAfterTest(); 2316 2317 $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D', 'P1D'); 2318 $purposes->course->set('protected', 0)->save(); 2319 $purposes->activity->set('protected', 1)->save(); 2320 2321 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2322 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() + WEEKSECS]); 2323 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 2324 2325 $coursecontext = \context_course::instance($course->id); 2326 $cm = get_coursemodule_from_instance('forum', $forum->id); 2327 $forumcontext = \context_module::instance($cm->id); 2328 2329 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2330 2331 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2332 $this->assertFalse(expired_contexts_manager::is_context_expired($forumcontext)); 2333 2334 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2335 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $user)); 2336 2337 $purposes->activity->set('protected', 0)->save(); 2338 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $user)); 2339 } 2340 2341 /** 2342 * Test the is_context_expired functions when supplied with an expired course which has role overrides. 2343 */ 2344 public function test_is_context_expired_course_expired_override() { 2345 global $DB; 2346 2347 $this->resetAfterTest(); 2348 2349 $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H'); 2350 2351 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2352 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2353 $coursecontext = \context_course::instance($course->id); 2354 $systemcontext = \context_system::instance(); 2355 2356 $role = $DB->get_record('role', ['shortname' => 'manager']); 2357 $override = new purpose_override(0, (object) [ 2358 'purposeid' => $purposes->course->get('id'), 2359 'roleid' => $role->id, 2360 'retentionperiod' => 'P5Y', 2361 ]); 2362 $override->save(); 2363 role_assign($role->id, $user->id, $systemcontext->id); 2364 2365 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2366 2367 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2368 2369 $purposes->course->set('protected', 1)->save(); 2370 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2371 2372 $purposes->course->set('protected', 0)->save(); 2373 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2374 } 2375 2376 /** 2377 * Test the is_context_expired functions when supplied with an expired course which has role overrides. 2378 */ 2379 public function test_is_context_expired_course_expired_override_parent() { 2380 global $DB; 2381 2382 $this->resetAfterTest(); 2383 2384 $purposes = $this->setup_basics('PT1H', 'PT1H'); 2385 2386 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2387 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2388 $coursecontext = \context_course::instance($course->id); 2389 $systemcontext = \context_system::instance(); 2390 2391 $role = $DB->get_record('role', ['shortname' => 'manager']); 2392 $override = new purpose_override(0, (object) [ 2393 'purposeid' => $purposes->system->get('id'), 2394 'roleid' => $role->id, 2395 'retentionperiod' => 'P5Y', 2396 ]); 2397 $override->save(); 2398 role_assign($role->id, $user->id, $systemcontext->id); 2399 2400 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2401 2402 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2403 2404 // The user override applies to this user. THIs means that the default expiry has no effect. 2405 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2406 2407 $purposes->system->set('protected', 1)->save(); 2408 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2409 2410 $purposes->system->set('protected', 0)->save(); 2411 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2412 2413 $override->set('protected', 1)->save(); 2414 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2415 2416 $purposes->system->set('protected', 1)->save(); 2417 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2418 2419 $purposes->system->set('protected', 0)->save(); 2420 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user)); 2421 2422 } 2423 2424 /** 2425 * Test the is_context_expired functions when supplied with an expired course which has role overrides but the user 2426 * does not hold the role. 2427 */ 2428 public function test_is_context_expired_course_expired_override_parent_no_role() { 2429 global $DB; 2430 2431 $this->resetAfterTest(); 2432 2433 $purposes = $this->setup_basics('PT1H', 'PT1H'); 2434 2435 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2436 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2437 $coursecontext = \context_course::instance($course->id); 2438 $systemcontext = \context_system::instance(); 2439 2440 $role = $DB->get_record('role', ['shortname' => 'manager']); 2441 $override = new purpose_override(0, (object) [ 2442 'purposeid' => $purposes->system->get('id'), 2443 'roleid' => $role->id, 2444 'retentionperiod' => 'P5Y', 2445 ]); 2446 $override->save(); 2447 2448 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2449 2450 // This context is not _fully _ expired. 2451 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2452 } 2453 2454 /** 2455 * Test the is_context_expired functions when supplied with an unexpired course which has role overrides. 2456 */ 2457 public function test_is_context_expired_course_expired_override_inverse() { 2458 global $DB; 2459 2460 $this->resetAfterTest(); 2461 2462 $purposes = $this->setup_basics('P1Y', 'P1Y'); 2463 2464 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2465 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2466 $coursecontext = \context_course::instance($course->id); 2467 $systemcontext = \context_system::instance(); 2468 2469 $role = $DB->get_record('role', ['shortname' => 'student']); 2470 $override = new purpose_override(0, (object) [ 2471 'purposeid' => $purposes->system->get('id'), 2472 'roleid' => $role->id, 2473 'retentionperiod' => 'PT1S', 2474 ]); 2475 $override->save(); 2476 2477 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2478 2479 // This context is not _fully _ expired. 2480 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2481 } 2482 2483 /** 2484 * Test the is_context_expired functions when supplied with an unexpired course which has role overrides. 2485 */ 2486 public function test_is_context_expired_course_expired_override_inverse_parent() { 2487 global $DB; 2488 2489 $this->resetAfterTest(); 2490 2491 $purposes = $this->setup_basics('P1Y', 'P1Y'); 2492 2493 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2494 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2495 $coursecontext = \context_course::instance($course->id); 2496 $systemcontext = \context_system::instance(); 2497 2498 $role = $DB->get_record('role', ['shortname' => 'manager']); 2499 $override = new purpose_override(0, (object) [ 2500 'purposeid' => $purposes->system->get('id'), 2501 'roleid' => $role->id, 2502 'retentionperiod' => 'PT1S', 2503 ]); 2504 $override->save(); 2505 2506 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2507 role_assign($role->id, $user->id, $systemcontext->id); 2508 2509 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 2510 role_unassign($studentrole->id, $user->id, $coursecontext->id); 2511 2512 // This context is not _fully _ expired. 2513 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2514 } 2515 2516 /** 2517 * Test the is_context_expired functions when supplied with an unexpired course which has role overrides. 2518 */ 2519 public function test_is_context_expired_course_expired_override_inverse_parent_not_assigned() { 2520 global $DB; 2521 2522 $this->resetAfterTest(); 2523 2524 $purposes = $this->setup_basics('P1Y', 'P1Y'); 2525 2526 $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]); 2527 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]); 2528 $coursecontext = \context_course::instance($course->id); 2529 $systemcontext = \context_system::instance(); 2530 2531 $role = $DB->get_record('role', ['shortname' => 'manager']); 2532 $override = new purpose_override(0, (object) [ 2533 'purposeid' => $purposes->system->get('id'), 2534 'roleid' => $role->id, 2535 'retentionperiod' => 'PT1S', 2536 ]); 2537 $override->save(); 2538 2539 // Enrol the user in the course without any role. 2540 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 2541 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 2542 role_unassign($studentrole->id, $user->id, $coursecontext->id); 2543 2544 // This context is not _fully _ expired. 2545 $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext)); 2546 } 2547 2548 /** 2549 * Ensure that context expired checks for a specific user taken into account roles. 2550 */ 2551 public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected() { 2552 global $DB; 2553 2554 $this->resetAfterTest(); 2555 2556 $purposes = $this->setup_basics('PT1S', 'PT1S', 'PT1S'); 2557 2558 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]); 2559 $coursecontext = \context_course::instance($course->id); 2560 $systemcontext = \context_system::instance(); 2561 2562 $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id'); 2563 $override = new purpose_override(0, (object) [ 2564 'purposeid' => $purposes->course->get('id'), 2565 'roleid' => $roles['manager'], 2566 'retentionperiod' => 'P1W', 2567 'protected' => 1, 2568 ]); 2569 $override->save(); 2570 2571 $s = $this->getDataGenerator()->create_user(); 2572 $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student'); 2573 2574 $t = $this->getDataGenerator()->create_user(); 2575 $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher'); 2576 2577 $sm = $this->getDataGenerator()->create_user(); 2578 $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student'); 2579 role_assign($roles['manager'], $sm->id, $coursecontext->id); 2580 2581 $m = $this->getDataGenerator()->create_user(); 2582 role_assign($roles['manager'], $m->id, $coursecontext->id); 2583 2584 $tm = $this->getDataGenerator()->create_user(); 2585 $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher'); 2586 role_assign($roles['manager'], $tm->id, $coursecontext->id); 2587 2588 // The context should only be expired for users who are not a manager. 2589 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s)); 2590 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t)); 2591 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm)); 2592 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm)); 2593 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m)); 2594 2595 $override->set('protected', 0)->save(); 2596 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s)); 2597 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t)); 2598 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm)); 2599 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm)); 2600 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m)); 2601 } 2602 2603 /** 2604 * Ensure that context expired checks for a specific user taken into account roles when retention is inversed. 2605 */ 2606 public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected_inverse() { 2607 global $DB; 2608 2609 $this->resetAfterTest(); 2610 2611 $purposes = $this->setup_basics('P5Y', 'P5Y', 'P5Y'); 2612 2613 $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]); 2614 $coursecontext = \context_course::instance($course->id); 2615 $systemcontext = \context_system::instance(); 2616 2617 $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id'); 2618 $override = new purpose_override(0, (object) [ 2619 'purposeid' => $purposes->course->get('id'), 2620 'roleid' => $roles['student'], 2621 'retentionperiod' => 'PT1S', 2622 ]); 2623 $override->save(); 2624 2625 $s = $this->getDataGenerator()->create_user(); 2626 $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student'); 2627 2628 $t = $this->getDataGenerator()->create_user(); 2629 $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher'); 2630 2631 $sm = $this->getDataGenerator()->create_user(); 2632 $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student'); 2633 role_assign($roles['manager'], $sm->id, $coursecontext->id); 2634 2635 $m = $this->getDataGenerator()->create_user(); 2636 role_assign($roles['manager'], $m->id, $coursecontext->id); 2637 2638 $tm = $this->getDataGenerator()->create_user(); 2639 $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher'); 2640 role_assign($roles['manager'], $tm->id, $coursecontext->id); 2641 2642 // The context should only be expired for users who are only a student. 2643 $purposes->course->set('protected', 1)->save(); 2644 $override->set('protected', 1)->save(); 2645 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s)); 2646 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t)); 2647 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm)); 2648 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm)); 2649 $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m)); 2650 2651 $purposes->course->set('protected', 0)->save(); 2652 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s)); 2653 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t)); 2654 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm)); 2655 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm)); 2656 $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m)); 2657 } 2658 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body