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