Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 */
1441 ariadna 26
final class expired_contexts_test extends \advanced_testcase {
1 efrain 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
     */
1441 ariadna 36
    protected function setup_basics(string $system, string $user, ?string $course = null, ?string $activity = null): \stdClass {
1 efrain 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
     */
11 efrain 93
    public function test_flag_not_setup(): void {
1 efrain 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
     */
11 efrain 114
    public function test_flag_user_no_lastaccess(): void {
1 efrain 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
     */
11 efrain 137
    public function test_flag_user_recent_lastaccess(): void {
1 efrain 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
     */
11 efrain 160
    public function test_flag_user_past_lastaccess(): void {
1 efrain 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
     */
11 efrain 184
    public function test_flag_user_past_lastaccess_still_enrolled(): void {
1 efrain 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
     */
11 efrain 212
    public function test_flag_user_update_existing(): void {
1 efrain 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
     */
11 efrain 244
    public function test_flag_user_past_lastaccess_unexpired_past_enrolment(): void {
1 efrain 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
     */
11 efrain 272
    public function test_flag_user_past_override_role(): void {
1 efrain 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
     */
11 efrain 306
    public function test_flag_user_past_lastaccess_expired_enrolled(): void {
1 efrain 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
     */
11 efrain 335
    public function test_flag_user_past_lastaccess_missing_enddate_required(): void {
1 efrain 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
     */
11 efrain 367
    public function test_flag_user_past_lastaccess_missing_enddate_not_required(): void {
1 efrain 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
     */
11 efrain 398
    public function test_flag_user_recent_lastaccess_existing_record(): void {
1 efrain 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
     */
11 efrain 432
    public function test_flag_user_retention_changed(): void {
1 efrain 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
     */
11 efrain 472
    public function test_flag_user_historic_block_unapproved(): void {
1 efrain 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
     */
11 efrain 509
    public function test_flag_user_historic_unexpired_child(): void {
1 efrain 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
     */
11 efrain 537
    public function test_flag_course_no_enddate(): void {
1 efrain 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
     */
11 efrain 556
    public function test_flag_course_past_enddate_future_child(): void {
1 efrain 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
     */
11 efrain 578
    public function test_flag_course_past_enddate(): void {
1 efrain 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
     */
11 efrain 600
    public function test_flag_course_past_enddate_multiple(): void {
1 efrain 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
     */
11 efrain 628
    public function test_flag_course_future_enddate(): void {
1 efrain 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
     */
11 efrain 647
    public function test_flag_course_recent_unexpired_enddate(): void {
1 efrain 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
     */
11 efrain 665
    public function test_flag_course_past_enddate_with_override_unexpired_role(): void {
1 efrain 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
     */
11 efrain 704
    public function test_flag_course_past_enddate_with_override_expired_role(): void {
1 efrain 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
     */
11 efrain 743
    public function test_flag_course_override_expiredwith_override_unexpired_on_child(): void {
1 efrain 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
     */
11 efrain 812
    public function test_process_user_context_with_override_unexpired_role(): void {
1 efrain 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
     */
11 efrain 870
    public function test_process_course_context_with_override_unexpired_role(): void {
1 efrain 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
     */
11 efrain 958
    public function test_process_course_context_with_override_expired_role(): void {
1 efrain 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
     */
11 efrain 1046
    public function test_process_course_context_with_user_in_both_lists(): void {
1 efrain 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
     */
11 efrain 1135
    public function test_process_course_context_with_user_in_both_lists_expired(): void {
1 efrain 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
     */
11 efrain 1231
    public function test_process_not_setup(): void {
1 efrain 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
     */
11 efrain 1268
    public function test_process_none_approved(): void {
1 efrain 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
     */
11 efrain 1307
    public function test_process_no_context(): void {
1 efrain 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
     */
11 efrain 1346
    public function test_process_user_context(): void {
1 efrain 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');
1441 ariadna 1373
        $deleteinvocations = $this->exactly(2);
1374
        $mockprivacymanager->expects($deleteinvocations)
1 efrain 1375
            ->method('delete_data_for_all_users_in_context')
1441 ariadna 1376
            ->willReturnCallback(function ($context) use (
1377
                $deleteinvocations,
1378
                $blockcontext,
1379
                $usercontext,
1380
            ): void {
1381
                match (self::getInvocationCount($deleteinvocations)) {
1382
                    1 => $this->assertEquals($blockcontext, $context),
1383
                    2 => $this->assertEquals($usercontext, $context),
1384
                    default => $this->fail('Unexpected invocation count'),
1385
                };
1386
            });
1 efrain 1387
 
1388
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1389
            ->onlyMethods(['get_privacy_manager'])
1390
            ->getMock();
1391
        $manager->set_progress(new \null_progress_trace());
1392
 
1393
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1394
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1395
 
1396
        $this->assertEquals(0, $processedcourses);
1397
        $this->assertEquals(1, $processedusers);
1398
 
1399
        $updatedcontext = new expired_context($expiredcontext->get('id'));
1400
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1401
 
1402
        // Flag all expired contexts again.
1403
        list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
1404
 
1405
        $this->assertEquals(0, $flaggedcourses);
1406
        $this->assertEquals(0, $flaggedusers);
1407
 
1408
        // Ensure that the deleted context record is still present.
1409
        $updatedcontext = new expired_context($expiredcontext->get('id'));
1410
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1411
    }
1412
 
1413
    /**
1414
     * Ensure that a course context previously flagged as approved is removed.
1415
     */
11 efrain 1416
    public function test_process_course_context(): void {
1 efrain 1417
        $this->resetAfterTest();
1418
 
1419
        $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1420
 
1421
        $course = $this->getDataGenerator()->create_course([
1422
                'startdate' => time() - (2 * YEARSECS),
1423
                'enddate' => time() - YEARSECS,
1424
            ]);
1425
        $coursecontext = \context_course::instance($course->id);
1426
 
1427
        // Create an existing expired_context.
1428
        $expiredcontext = new expired_context(0, (object) [
1429
                'contextid' => $coursecontext->id,
1430
                'status' => expired_context::STATUS_APPROVED,
1431
            ]);
1432
        $expiredcontext->save();
1433
 
1434
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1435
            ->onlyMethods([
1436
                'delete_data_for_user',
1437
                'delete_data_for_all_users_in_context',
1438
            ])
1439
            ->getMock();
1440
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1441
        $mockprivacymanager->expects($this->once())->method('delete_data_for_all_users_in_context');
1442
 
1443
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1444
            ->onlyMethods(['get_privacy_manager'])
1445
            ->getMock();
1446
        $manager->set_progress(new \null_progress_trace());
1447
 
1448
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1449
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1450
 
1451
        $this->assertEquals(1, $processedcourses);
1452
        $this->assertEquals(0, $processedusers);
1453
 
1454
        $updatedcontext = new expired_context($expiredcontext->get('id'));
1455
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1456
    }
1457
 
1458
    /**
1459
     * Ensure that a user context previously flagged as approved is not removed if the user then logs in.
1460
     */
11 efrain 1461
    public function test_process_user_context_logged_in_after_approval(): void {
1 efrain 1462
        $this->resetAfterTest();
1463
 
1464
        $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1465
 
1466
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
1467
        $usercontext = \context_user::instance($user->id);
1468
 
1469
        $this->setUser($user);
1470
        $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
1471
        $context = \context_block::instance($block->instance->id);
1472
        $this->setUser();
1473
 
1474
        // Create an existing expired_context.
1475
        $expiredcontext = new expired_context(0, (object) [
1476
                'contextid' => $usercontext->id,
1477
                'status' => expired_context::STATUS_APPROVED,
1478
            ]);
1479
        $expiredcontext->save();
1480
 
1481
        // Now bump the user's last login time.
1482
        $this->setUser($user);
1483
        user_accesstime_log();
1484
        $this->setUser();
1485
 
1486
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1487
            ->onlyMethods([
1488
                'delete_data_for_user',
1489
                'delete_data_for_all_users_in_context',
1490
            ])
1491
            ->getMock();
1492
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1493
        $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1494
 
1495
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1496
            ->onlyMethods(['get_privacy_manager'])
1497
            ->getMock();
1498
        $manager->set_progress(new \null_progress_trace());
1499
 
1500
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1501
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1502
 
1503
        $this->assertEquals(0, $processedcourses);
1504
        $this->assertEquals(0, $processedusers);
1505
 
1506
        $this->expectException('dml_missing_record_exception');
1507
        new expired_context($expiredcontext->get('id'));
1508
    }
1509
 
1510
    /**
1511
     * Ensure that a user context previously flagged as approved is not removed if the purpose has changed.
1512
     */
11 efrain 1513
    public function test_process_user_context_changed_after_approved(): void {
1 efrain 1514
        $this->resetAfterTest();
1515
 
1516
        $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1517
 
1518
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
1519
        $usercontext = \context_user::instance($user->id);
1520
 
1521
        $this->setUser($user);
1522
        $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
1523
        $context = \context_block::instance($block->instance->id);
1524
        $this->setUser();
1525
 
1526
        // Create an existing expired_context.
1527
        $expiredcontext = new expired_context(0, (object) [
1528
                'contextid' => $usercontext->id,
1529
                'status' => expired_context::STATUS_APPROVED,
1530
            ]);
1531
        $expiredcontext->save();
1532
 
1533
        // Now make the user a site admin.
1534
        $admins = explode(',', get_config('moodle', 'siteadmins'));
1535
        $admins[] = $user->id;
1536
        set_config('siteadmins', implode(',', $admins));
1537
 
1538
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1539
            ->onlyMethods([
1540
                'delete_data_for_user',
1541
                'delete_data_for_all_users_in_context',
1542
            ])
1543
            ->getMock();
1544
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1545
        $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1546
 
1547
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1548
            ->onlyMethods(['get_privacy_manager'])
1549
            ->getMock();
1550
        $manager->set_progress(new \null_progress_trace());
1551
 
1552
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1553
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1554
 
1555
        $this->assertEquals(0, $processedcourses);
1556
        $this->assertEquals(0, $processedusers);
1557
 
1558
        $this->expectException('dml_missing_record_exception');
1559
        new expired_context($expiredcontext->get('id'));
1560
    }
1561
 
1562
    /**
1563
     * Ensure that a user with a historically expired expired block record child is cleaned up.
1564
     */
11 efrain 1565
    public function test_process_user_historic_block_unapproved(): void {
1 efrain 1566
        $this->resetAfterTest();
1567
 
1568
        $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1569
 
1570
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
1571
        $usercontext = \context_user::instance($user->id);
1572
 
1573
        $this->setUser($user);
1574
        $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
1575
        $blockcontext = \context_block::instance($block->instance->id);
1576
        $this->setUser();
1577
 
1578
        // Create an expired_context for the user.
1579
        $expiredusercontext = new expired_context(0, (object) [
1580
                'contextid' => $usercontext->id,
1581
                'status' => expired_context::STATUS_APPROVED,
1582
            ]);
1583
        $expiredusercontext->save();
1584
 
1585
        // Create an existing expired_context which has not been approved for the block.
1586
        $expiredblockcontext = new expired_context(0, (object) [
1587
                'contextid' => $blockcontext->id,
1588
                'status' => expired_context::STATUS_EXPIRED,
1589
            ]);
1590
        $expiredblockcontext->save();
1591
 
1592
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1593
            ->onlyMethods([
1594
                'delete_data_for_user',
1595
                'delete_data_for_all_users_in_context',
1596
            ])
1597
            ->getMock();
1598
        $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
1441 ariadna 1599
        $deleteinvocations = $this->exactly(2);
1600
        $mockprivacymanager->expects($deleteinvocations)
1 efrain 1601
            ->method('delete_data_for_all_users_in_context')
1441 ariadna 1602
            ->willReturnCallback(function ($context) use (
1603
                $deleteinvocations,
1604
                $blockcontext,
1605
                $usercontext,
1606
            ): void {
1607
                match (self::getInvocationCount($deleteinvocations)) {
1608
                    1 => $this->assertEquals($blockcontext, $context),
1609
                    2 => $this->assertEquals($usercontext, $context),
1610
                    default => $this->fail('Unexpected invocation count'),
1611
                };
1612
            });
1 efrain 1613
 
1614
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1615
            ->onlyMethods(['get_privacy_manager'])
1616
            ->getMock();
1617
        $manager->set_progress(new \null_progress_trace());
1618
 
1619
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1620
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1621
 
1622
        $this->assertEquals(0, $processedcourses);
1623
        $this->assertEquals(1, $processedusers);
1624
 
1625
        $updatedcontext = new expired_context($expiredusercontext->get('id'));
1626
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1627
    }
1628
 
1629
    /**
1630
     * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
1631
     */
11 efrain 1632
    public function test_process_user_historic_unexpired_child(): void {
1 efrain 1633
        $this->resetAfterTest();
1634
 
1635
        $this->setup_basics('PT1H', 'PT1H', 'PT1H');
1636
        $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
1637
 
1638
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
1639
        $usercontext = \context_user::instance($user->id);
1640
 
1641
        $this->setUser($user);
1642
        $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
1643
        $blockcontext = \context_block::instance($block->instance->id);
1644
        $this->setUser();
1645
 
1646
        // Create an expired_context for the user.
1647
        $expiredusercontext = new expired_context(0, (object) [
1648
                'contextid' => $usercontext->id,
1649
                'status' => expired_context::STATUS_APPROVED,
1650
            ]);
1651
        $expiredusercontext->save();
1652
 
1653
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1654
            ->onlyMethods([
1655
                'delete_data_for_user',
1656
                'delete_data_for_all_users_in_context',
1657
            ])
1658
            ->getMock();
1659
        $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
1441 ariadna 1660
        $deleteinvocations = $this->exactly(2);
1661
        $mockprivacymanager->expects($deleteinvocations)
1 efrain 1662
            ->method('delete_data_for_all_users_in_context')
1441 ariadna 1663
            ->willReturnCallback(function ($context) use (
1664
                $deleteinvocations,
1665
                $blockcontext,
1666
                $usercontext,
1667
            ): void {
1668
                match (self::getInvocationCount($deleteinvocations)) {
1669
                    1 => $this->assertEquals($blockcontext, $context),
1670
                    2 => $this->assertEquals($usercontext, $context),
1671
                    default => $this->fail('Unexpected invocation count'),
1672
                };
1673
            });
1 efrain 1674
 
1675
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1676
            ->onlyMethods(['get_privacy_manager'])
1677
            ->getMock();
1678
        $manager->set_progress(new \null_progress_trace());
1679
 
1680
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1681
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1682
 
1683
        $this->assertEquals(0, $processedcourses);
1684
        $this->assertEquals(1, $processedusers);
1685
 
1686
        $updatedcontext = new expired_context($expiredusercontext->get('id'));
1687
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1688
    }
1689
 
1690
    /**
1691
     * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1692
     * updated.
1693
     */
11 efrain 1694
    public function test_process_course_context_updated(): void {
1 efrain 1695
        $this->resetAfterTest();
1696
 
1697
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1698
 
1699
        $course = $this->getDataGenerator()->create_course([
1700
                'startdate' => time() - (2 * YEARSECS),
1701
                'enddate' => time() - YEARSECS,
1702
            ]);
1703
        $coursecontext = \context_course::instance($course->id);
1704
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1705
 
1706
        // Create an existing expired_context.
1707
        $expiredcontext = new expired_context(0, (object) [
1708
                'contextid' => $coursecontext->id,
1709
                'status' => expired_context::STATUS_APPROVED,
1710
            ]);
1711
        $expiredcontext->save();
1712
 
1713
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1714
            ->onlyMethods([
1715
                'delete_data_for_user',
1716
                'delete_data_for_all_users_in_context',
1717
            ])
1718
            ->getMock();
1719
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1720
        $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1721
 
1722
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1723
            ->onlyMethods(['get_privacy_manager'])
1724
            ->getMock();
1725
        $manager->set_progress(new \null_progress_trace());
1726
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1727
 
1728
        // Changing the retention period to a longer period will remove the expired_context record.
1729
        $purposes->activity->set('retentionperiod', 'P5Y');
1730
        $purposes->activity->save();
1731
 
1732
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1733
 
1734
        $this->assertEquals(0, $processedcourses);
1735
        $this->assertEquals(0, $processedusers);
1736
 
1737
        $this->expectException('dml_missing_record_exception');
1738
        $updatedcontext = new expired_context($expiredcontext->get('id'));
1739
    }
1740
 
1741
    /**
1742
     * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1743
     * updated.
1744
     */
11 efrain 1745
    public function test_process_course_context_outstanding_children(): void {
1 efrain 1746
        $this->resetAfterTest();
1747
 
1748
        $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1749
 
1750
        $course = $this->getDataGenerator()->create_course([
1751
                'startdate' => time() - (2 * YEARSECS),
1752
                'enddate' => time() - YEARSECS,
1753
            ]);
1754
        $coursecontext = \context_course::instance($course->id);
1755
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1756
 
1757
        // Create an existing expired_context.
1758
        $expiredcontext = new expired_context(0, (object) [
1759
                'contextid' => $coursecontext->id,
1760
                'status' => expired_context::STATUS_APPROVED,
1761
            ]);
1762
        $expiredcontext->save();
1763
 
1764
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1765
            ->onlyMethods([
1766
                'delete_data_for_user',
1767
                'delete_data_for_all_users_in_context',
1768
            ])
1769
            ->getMock();
1770
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1771
        $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1772
 
1773
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1774
            ->onlyMethods(['get_privacy_manager'])
1775
            ->getMock();
1776
        $manager->set_progress(new \null_progress_trace());
1777
 
1778
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1779
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1780
 
1781
        $this->assertEquals(0, $processedcourses);
1782
        $this->assertEquals(0, $processedusers);
1783
 
1784
        $updatedcontext = new expired_context($expiredcontext->get('id'));
1785
 
1786
        // No change - we just can't process it until the children have finished.
1787
        $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1788
    }
1789
 
1790
    /**
1791
     * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1792
     * updated.
1793
     */
11 efrain 1794
    public function test_process_course_context_pending_children(): void {
1 efrain 1795
        $this->resetAfterTest();
1796
 
1797
        $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1798
 
1799
        $course = $this->getDataGenerator()->create_course([
1800
                'startdate' => time() - (2 * YEARSECS),
1801
                'enddate' => time() - YEARSECS,
1802
            ]);
1803
        $coursecontext = \context_course::instance($course->id);
1804
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1805
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1806
        $forumcontext = \context_module::instance($cm->id);
1807
 
1808
        // Create an existing expired_context for the course.
1809
        $expiredcoursecontext = new expired_context(0, (object) [
1810
                'contextid' => $coursecontext->id,
1811
                'status' => expired_context::STATUS_APPROVED,
1812
            ]);
1813
        $expiredcoursecontext->save();
1814
 
1815
        // And for the forum.
1816
        $expiredforumcontext = new expired_context(0, (object) [
1817
                'contextid' => $forumcontext->id,
1818
                'status' => expired_context::STATUS_EXPIRED,
1819
            ]);
1820
        $expiredforumcontext->save();
1821
 
1822
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1823
            ->onlyMethods([
1824
                'delete_data_for_user',
1825
                'delete_data_for_all_users_in_context',
1826
            ])
1827
            ->getMock();
1828
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1829
        $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1830
 
1831
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1832
            ->onlyMethods(['get_privacy_manager'])
1833
            ->getMock();
1834
        $manager->set_progress(new \null_progress_trace());
1835
 
1836
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1837
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1838
 
1839
        $this->assertEquals(0, $processedcourses);
1840
        $this->assertEquals(0, $processedusers);
1841
 
1842
        $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1843
 
1844
        // No change - we just can't process it until the children have finished.
1845
        $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1846
    }
1847
 
1848
    /**
1849
     * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1850
     * updated.
1851
     */
11 efrain 1852
    public function test_process_course_context_approved_children(): void {
1 efrain 1853
        $this->resetAfterTest();
1854
 
1855
        $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1856
 
1857
        $course = $this->getDataGenerator()->create_course([
1858
                'startdate' => time() - (2 * YEARSECS),
1859
                'enddate' => time() - YEARSECS,
1860
            ]);
1861
        $coursecontext = \context_course::instance($course->id);
1862
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1863
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1864
        $forumcontext = \context_module::instance($cm->id);
1865
 
1866
        // Create an existing expired_context for the course.
1867
        $expiredcoursecontext = new expired_context(0, (object) [
1868
                'contextid' => $coursecontext->id,
1869
                'status' => expired_context::STATUS_APPROVED,
1870
            ]);
1871
        $expiredcoursecontext->save();
1872
 
1873
        // And for the forum.
1874
        $expiredforumcontext = new expired_context(0, (object) [
1875
                'contextid' => $forumcontext->id,
1876
                'status' => expired_context::STATUS_APPROVED,
1877
            ]);
1878
        $expiredforumcontext->save();
1879
 
1880
        $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1881
            ->onlyMethods([
1882
                'delete_data_for_user',
1883
                'delete_data_for_all_users_in_context',
1884
            ])
1885
            ->getMock();
1886
        $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1441 ariadna 1887
        $deleteinvocations = $this->exactly(2);
1888
        $mockprivacymanager->expects($deleteinvocations)
1 efrain 1889
            ->method('delete_data_for_all_users_in_context')
1441 ariadna 1890
            ->willReturnCallback(function ($context) use (
1891
                $deleteinvocations,
1892
                $forumcontext,
1893
                $coursecontext,
1894
            ): void {
1895
                match (self::getInvocationCount($deleteinvocations)) {
1896
                    1 => $this->assertEquals($forumcontext, $context),
1897
                    2 => $this->assertEquals($coursecontext, $context),
1898
                    default => $this->fail('Unexpected invocation count'),
1899
                };
1900
            });
1 efrain 1901
 
1902
        $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1903
            ->onlyMethods(['get_privacy_manager'])
1904
            ->getMock();
1905
        $manager->set_progress(new \null_progress_trace());
1906
 
1907
        $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1908
 
1909
        // Initially only the forum will be processed.
1910
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1911
 
1912
        $this->assertEquals(1, $processedcourses);
1913
        $this->assertEquals(0, $processedusers);
1914
 
1915
        $updatedcontext = new expired_context($expiredforumcontext->get('id'));
1916
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1917
 
1918
        // The course won't have been processed yet.
1919
        $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1920
        $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1921
 
1922
        // A subsequent run will cause the course to processed as it is no longer dependent upon the child contexts.
1923
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1924
 
1925
        $this->assertEquals(1, $processedcourses);
1926
        $this->assertEquals(0, $processedusers);
1927
        $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1928
        $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1929
    }
1930
 
1931
    /**
1932
     * Test that the can_process_deletion function returns expected results.
1933
     *
1934
     * @dataProvider    can_process_deletion_provider
1935
     * @param       int     $status
1936
     * @param       bool    $expected
1937
     */
11 efrain 1938
    public function test_can_process_deletion($status, $expected): void {
1 efrain 1939
        $purpose = new expired_context(0, (object) [
1940
            'status' => $status,
1941
 
1942
            'contextid' => \context_system::instance()->id,
1943
        ]);
1944
 
1945
        $this->assertEquals($expected, $purpose->can_process_deletion());
1946
    }
1947
 
1948
    /**
1949
     * Data provider for the can_process_deletion tests.
1950
     *
1951
     * @return  array
1952
     */
1441 ariadna 1953
    public static function can_process_deletion_provider(): array {
1 efrain 1954
        return [
1955
            'Pending' => [
1956
                expired_context::STATUS_EXPIRED,
1957
                false,
1958
            ],
1959
            'Approved' => [
1960
                expired_context::STATUS_APPROVED,
1961
                true,
1962
            ],
1963
            'Complete' => [
1964
                expired_context::STATUS_CLEANED,
1965
                false,
1966
            ],
1967
        ];
1968
    }
1969
 
1970
    /**
1971
     * Test that the is_complete function returns expected results.
1972
     *
1973
     * @dataProvider        is_complete_provider
1974
     * @param       int     $status
1975
     * @param       bool    $expected
1976
     */
11 efrain 1977
    public function test_is_complete($status, $expected): void {
1 efrain 1978
        $purpose = new expired_context(0, (object) [
1979
            'status' => $status,
1980
            'contextid' => \context_system::instance()->id,
1981
        ]);
1982
 
1983
        $this->assertEquals($expected, $purpose->is_complete());
1984
    }
1985
 
1986
    /**
1987
     * Data provider for the is_complete tests.
1988
     *
1989
     * @return  array
1990
     */
1441 ariadna 1991
    public static function is_complete_provider(): array {
1 efrain 1992
        return [
1993
            'Pending' => [
1994
                expired_context::STATUS_EXPIRED,
1995
                false,
1996
            ],
1997
            'Approved' => [
1998
                expired_context::STATUS_APPROVED,
1999
                false,
2000
            ],
2001
            'Complete' => [
2002
                expired_context::STATUS_CLEANED,
2003
                true,
2004
            ],
2005
        ];
2006
    }
2007
 
2008
    /**
2009
     * Test that the is_fully_expired function returns expected results.
2010
     *
2011
     * @dataProvider        is_fully_expired_provider
2012
     * @param       array   $record
2013
     * @param       bool    $expected
2014
     */
11 efrain 2015
    public function test_is_fully_expired($record, $expected): void {
1 efrain 2016
        $purpose = new expired_context(0, (object) $record);
2017
 
2018
        $this->assertEquals($expected, $purpose->is_fully_expired());
2019
    }
2020
 
2021
    /**
2022
     * Data provider for the is_fully_expired tests.
2023
     *
2024
     * @return  array
2025
     */
1441 ariadna 2026
    public static function is_fully_expired_provider(): array {
1 efrain 2027
        return [
2028
            'Fully expired' => [
2029
                [
2030
                    'status' => expired_context::STATUS_APPROVED,
2031
                    'defaultexpired' => 1,
2032
                ],
2033
                true,
2034
            ],
2035
            'Unexpired roles present' => [
2036
                [
2037
                    'status' => expired_context::STATUS_APPROVED,
2038
                    'defaultexpired' => 1,
2039
                    'unexpiredroles' => json_encode([1]),
2040
                ],
2041
                false,
2042
            ],
2043
            'Only some expired roles present' => [
2044
                [
2045
                    'status' => expired_context::STATUS_APPROVED,
2046
                    'defaultexpired' => 0,
2047
                    'expiredroles' => json_encode([1]),
2048
                ],
2049
                false,
2050
            ],
2051
        ];
2052
    }
2053
 
2054
    /**
2055
     * Ensure that any orphaned records are removed once the context has been removed.
2056
     */
11 efrain 2057
    public function test_orphaned_records_are_cleared(): void {
1 efrain 2058
        $this->resetAfterTest();
2059
 
2060
        $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
2061
 
2062
        $course = $this->getDataGenerator()->create_course([
2063
                'startdate' => time() - (2 * YEARSECS),
2064
                'enddate' => time() - YEARSECS,
2065
            ]);
2066
        $context = \context_course::instance($course->id);
2067
 
2068
        // Flag all expired contexts.
2069
        $manager = new \tool_dataprivacy\expired_contexts_manager();
2070
        $manager->set_progress(new \null_progress_trace());
2071
        list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
2072
 
2073
        $this->assertEquals(1, $flaggedcourses);
2074
        $this->assertEquals(0, $flaggedusers);
2075
 
2076
        // Ensure that the record currently exists.
2077
        $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
2078
        $this->assertNotFalse($expiredcontext);
2079
 
2080
        // Approve it.
2081
        $expiredcontext->set('status', expired_context::STATUS_APPROVED)->save();
2082
 
2083
        // Process deletions.
2084
        list($processedcourses, $processedusers) = $manager->process_approved_deletions();
2085
 
2086
        $this->assertEquals(1, $processedcourses);
2087
        $this->assertEquals(0, $processedusers);
2088
 
2089
        // Ensure that the record still exists.
2090
        $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
2091
        $this->assertNotFalse($expiredcontext);
2092
 
2093
        // Remove the actual course.
2094
        delete_course($course->id, false);
2095
 
2096
        // The record will still exist until we flag it again.
2097
        $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
2098
        $this->assertNotFalse($expiredcontext);
2099
 
2100
        list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
2101
        $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
2102
        $this->assertFalse($expiredcontext);
2103
    }
2104
 
2105
    /**
2106
     * Ensure that the progres tracer works as expected out of the box.
2107
     */
11 efrain 2108
    public function test_progress_tracer_default(): void {
1 efrain 2109
        $manager = new \tool_dataprivacy\expired_contexts_manager();
2110
 
2111
        $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
2112
        $rcm = $rc->getMethod('get_progress');
2113
 
2114
        $this->assertInstanceOf(\text_progress_trace::class, $rcm->invoke($manager));
2115
    }
2116
 
2117
    /**
2118
     * Ensure that the progres tracer works as expected when given a specific traer.
2119
     */
11 efrain 2120
    public function test_progress_tracer_set(): void {
1 efrain 2121
        $manager = new \tool_dataprivacy\expired_contexts_manager();
2122
        $mytrace = new \null_progress_trace();
2123
        $manager->set_progress($mytrace);
2124
 
2125
        $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
2126
        $rcm = $rc->getMethod('get_progress');
2127
 
2128
        $this->assertSame($mytrace, $rcm->invoke($manager));
2129
    }
2130
 
2131
    /**
2132
     * Creates an HTML block on a user.
2133
     *
2134
     * @param   string  $title
2135
     * @param   string  $body
2136
     * @param   string  $format
2137
     * @return  \block_instance
2138
     */
2139
    protected function create_user_block($title, $body, $format) {
2140
        global $USER;
2141
 
2142
        $configdata = (object) [
2143
            'title' => $title,
2144
            'text' => [
2145
                'itemid' => 19,
2146
                'text' => $body,
2147
                'format' => $format,
2148
            ],
2149
        ];
2150
 
2151
        $this->create_block($this->construct_user_page($USER));
2152
        $block = $this->get_last_block_on_page($this->construct_user_page($USER));
2153
        $block = block_instance('html', $block->instance);
2154
        $block->instance_config_save((object) $configdata);
2155
 
2156
        return $block;
2157
    }
2158
 
2159
    /**
2160
     * Creates an HTML block on a page.
2161
     *
2162
     * @param \page $page Page
2163
     */
2164
    protected function create_block($page) {
2165
        $page->blocks->add_block_at_end_of_default_region('html');
2166
    }
2167
 
2168
    /**
2169
     * Constructs a Page object for the User Dashboard.
2170
     *
2171
     * @param   \stdClass       $user User to create Dashboard for.
2172
     * @return  \moodle_page
2173
     */
2174
    protected function construct_user_page(\stdClass $user) {
2175
        $page = new \moodle_page();
2176
        $page->set_context(\context_user::instance($user->id));
2177
        $page->set_pagelayout('mydashboard');
2178
        $page->set_pagetype('my-index');
2179
        $page->blocks->load_blocks();
2180
        return $page;
2181
    }
2182
 
2183
    /**
2184
     * Get the last block on the page.
2185
     *
2186
     * @param \page $page Page
2187
     * @return \block_html Block instance object
2188
     */
2189
    protected function get_last_block_on_page($page) {
2190
        $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
2191
        $block = end($blocks);
2192
 
2193
        return $block;
2194
    }
2195
 
2196
    /**
2197
     * Test the is_context_expired functions when supplied with the system context.
2198
     */
11 efrain 2199
    public function test_is_context_expired_system(): void {
1 efrain 2200
        $this->resetAfterTest();
2201
        $this->setup_basics('PT1H', 'PT1H', 'P1D');
2202
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2203
 
2204
        $this->assertFalse(expired_contexts_manager::is_context_expired(\context_system::instance()));
2205
        $this->assertFalse(
2206
                expired_contexts_manager::is_context_expired_or_unprotected_for_user(\context_system::instance(), $user));
2207
    }
2208
 
2209
    /**
2210
     * Test the is_context_expired functions when supplied with a block in the user context.
2211
     *
2212
     * Children of a user context always follow the user expiry rather than any context level defaults (e.g. at the
2213
     * block level.
2214
     */
11 efrain 2215
    public function test_is_context_expired_user_block(): void {
1 efrain 2216
        $this->resetAfterTest();
2217
 
2218
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
2219
        $purposes->block = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
2220
 
2221
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2222
        $this->setUser($user);
2223
        $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
2224
        $blockcontext = \context_block::instance($block->instance->id);
2225
        $this->setUser();
2226
 
2227
        // Protected flags have no bearing on expiry of user subcontexts.
2228
        $this->assertTrue(expired_contexts_manager::is_context_expired($blockcontext));
2229
 
2230
        $purposes->block->set('protected', 1)->save();
2231
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
2232
 
2233
        $purposes->block->set('protected', 0)->save();
2234
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
2235
    }
2236
 
2237
    /**
2238
     * Test the is_context_expired functions when supplied with the front page course.
2239
     */
11 efrain 2240
    public function test_is_context_expired_frontpage(): void {
1 efrain 2241
        $this->resetAfterTest();
2242
 
2243
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
2244
 
2245
        $frontcourse = get_site();
2246
        $frontcoursecontext = \context_course::instance($frontcourse->id);
2247
 
2248
        $sitenews = $this->getDataGenerator()->create_module('forum', ['course' => $frontcourse->id]);
2249
        $cm = get_coursemodule_from_instance('forum', $sitenews->id);
2250
        $sitenewscontext = \context_module::instance($cm->id);
2251
 
2252
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2253
 
2254
        $this->assertFalse(expired_contexts_manager::is_context_expired($frontcoursecontext));
2255
        $this->assertFalse(expired_contexts_manager::is_context_expired($sitenewscontext));
2256
 
2257
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
2258
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
2259
 
2260
        // Protecting the course contextlevel does not impact the front page.
2261
        $purposes->course->set('protected', 1)->save();
2262
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
2263
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
2264
 
2265
        // Protecting the system contextlevel affects the front page, too.
2266
        $purposes->system->set('protected', 1)->save();
2267
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
2268
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
2269
    }
2270
 
2271
    /**
2272
     * Test the is_context_expired functions when supplied with an expired course.
2273
     */
11 efrain 2274
    public function test_is_context_expired_course_expired(): void {
1 efrain 2275
        $this->resetAfterTest();
2276
 
2277
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
2278
 
2279
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2280
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
2281
        $coursecontext = \context_course::instance($course->id);
2282
 
2283
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2284
 
2285
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2286
 
2287
        $purposes->course->set('protected', 1)->save();
2288
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2289
 
2290
        $purposes->course->set('protected', 0)->save();
2291
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2292
    }
2293
 
2294
    /**
2295
     * Test the is_context_expired functions when supplied with an unexpired course.
2296
     */
11 efrain 2297
    public function test_is_context_expired_course_unexpired(): void {
1 efrain 2298
        $this->resetAfterTest();
2299
 
2300
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
2301
 
2302
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2303
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2304
        $coursecontext = \context_course::instance($course->id);
2305
 
2306
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2307
 
2308
        $this->assertTrue(expired_contexts_manager::is_context_expired($coursecontext));
2309
 
2310
        $purposes->course->set('protected', 1)->save();
2311
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2312
 
2313
        $purposes->course->set('protected', 0)->save();
2314
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2315
    }
2316
 
2317
    /**
2318
     * Test the is_context_expired functions when supplied with an unexpired course and a child context in the course which is protected.
2319
     *
2320
     * When a child context has a specific purpose set, then that purpose should be respected with respect to the
2321
     * course.
2322
     *
2323
     * If the course is still within the expiry period for the child context, then that child's protected flag should be
2324
     * respected, even when the course may have expired.
2325
     */
11 efrain 2326
    public function test_is_child_context_expired_course_unexpired_with_child(): void {
1 efrain 2327
        $this->resetAfterTest();
2328
 
2329
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D', 'P1D');
2330
        $purposes->course->set('protected', 0)->save();
2331
        $purposes->activity->set('protected', 1)->save();
2332
 
2333
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2334
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() + WEEKSECS]);
2335
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
2336
 
2337
        $coursecontext = \context_course::instance($course->id);
2338
        $cm = get_coursemodule_from_instance('forum', $forum->id);
2339
        $forumcontext = \context_module::instance($cm->id);
2340
 
2341
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2342
 
2343
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2344
        $this->assertFalse(expired_contexts_manager::is_context_expired($forumcontext));
2345
 
2346
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2347
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $user));
2348
 
2349
        $purposes->activity->set('protected', 0)->save();
2350
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($forumcontext, $user));
2351
    }
2352
 
2353
    /**
2354
     * Test the is_context_expired functions when supplied with an expired course which has role overrides.
2355
     */
11 efrain 2356
    public function test_is_context_expired_course_expired_override(): void {
1 efrain 2357
        global $DB;
2358
 
2359
        $this->resetAfterTest();
2360
 
2361
        $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
2362
 
2363
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2364
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2365
        $coursecontext = \context_course::instance($course->id);
2366
        $systemcontext = \context_system::instance();
2367
 
2368
        $role = $DB->get_record('role', ['shortname' => 'manager']);
2369
        $override = new purpose_override(0, (object) [
2370
                'purposeid' => $purposes->course->get('id'),
2371
                'roleid' => $role->id,
2372
                'retentionperiod' => 'P5Y',
2373
            ]);
2374
        $override->save();
2375
        role_assign($role->id, $user->id, $systemcontext->id);
2376
 
2377
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2378
 
2379
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2380
 
2381
        $purposes->course->set('protected', 1)->save();
2382
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2383
 
2384
        $purposes->course->set('protected', 0)->save();
2385
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2386
    }
2387
 
2388
    /**
2389
     * Test the is_context_expired functions when supplied with an expired course which has role overrides.
2390
     */
11 efrain 2391
    public function test_is_context_expired_course_expired_override_parent(): void {
1 efrain 2392
        global $DB;
2393
 
2394
        $this->resetAfterTest();
2395
 
2396
        $purposes = $this->setup_basics('PT1H', 'PT1H');
2397
 
2398
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2399
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2400
        $coursecontext = \context_course::instance($course->id);
2401
        $systemcontext = \context_system::instance();
2402
 
2403
        $role = $DB->get_record('role', ['shortname' => 'manager']);
2404
        $override = new purpose_override(0, (object) [
2405
                'purposeid' => $purposes->system->get('id'),
2406
                'roleid' => $role->id,
2407
                'retentionperiod' => 'P5Y',
2408
            ]);
2409
        $override->save();
2410
        role_assign($role->id, $user->id, $systemcontext->id);
2411
 
2412
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2413
 
2414
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2415
 
2416
        // The user override applies to this user. THIs means that the default expiry has no effect.
2417
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2418
 
2419
        $purposes->system->set('protected', 1)->save();
2420
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2421
 
2422
        $purposes->system->set('protected', 0)->save();
2423
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2424
 
2425
        $override->set('protected', 1)->save();
2426
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2427
 
2428
        $purposes->system->set('protected', 1)->save();
2429
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2430
 
2431
        $purposes->system->set('protected', 0)->save();
2432
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $user));
2433
 
2434
    }
2435
 
2436
    /**
2437
     * Test the is_context_expired functions when supplied with an expired course which has role overrides but the user
2438
     * does not hold the role.
2439
     */
11 efrain 2440
    public function test_is_context_expired_course_expired_override_parent_no_role(): void {
1 efrain 2441
        global $DB;
2442
 
2443
        $this->resetAfterTest();
2444
 
2445
        $purposes = $this->setup_basics('PT1H', 'PT1H');
2446
 
2447
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2448
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2449
        $coursecontext = \context_course::instance($course->id);
2450
        $systemcontext = \context_system::instance();
2451
 
2452
        $role = $DB->get_record('role', ['shortname' => 'manager']);
2453
        $override = new purpose_override(0, (object) [
2454
                'purposeid' => $purposes->system->get('id'),
2455
                'roleid' => $role->id,
2456
                'retentionperiod' => 'P5Y',
2457
            ]);
2458
        $override->save();
2459
 
2460
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2461
 
2462
        // This context is not _fully _ expired.
2463
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2464
    }
2465
 
2466
    /**
2467
     * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
2468
     */
11 efrain 2469
    public function test_is_context_expired_course_expired_override_inverse(): void {
1 efrain 2470
        global $DB;
2471
 
2472
        $this->resetAfterTest();
2473
 
2474
        $purposes = $this->setup_basics('P1Y', 'P1Y');
2475
 
2476
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2477
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2478
        $coursecontext = \context_course::instance($course->id);
2479
        $systemcontext = \context_system::instance();
2480
 
2481
        $role = $DB->get_record('role', ['shortname' => 'student']);
2482
        $override = new purpose_override(0, (object) [
2483
                'purposeid' => $purposes->system->get('id'),
2484
                'roleid' => $role->id,
2485
                'retentionperiod' => 'PT1S',
2486
            ]);
2487
        $override->save();
2488
 
2489
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2490
 
2491
        // This context is not _fully _ expired.
2492
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2493
    }
2494
 
2495
    /**
2496
     * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
2497
     */
11 efrain 2498
    public function test_is_context_expired_course_expired_override_inverse_parent(): void {
1 efrain 2499
        global $DB;
2500
 
2501
        $this->resetAfterTest();
2502
 
2503
        $purposes = $this->setup_basics('P1Y', 'P1Y');
2504
 
2505
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2506
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2507
        $coursecontext = \context_course::instance($course->id);
2508
        $systemcontext = \context_system::instance();
2509
 
2510
        $role = $DB->get_record('role', ['shortname' => 'manager']);
2511
        $override = new purpose_override(0, (object) [
2512
                'purposeid' => $purposes->system->get('id'),
2513
                'roleid' => $role->id,
2514
                'retentionperiod' => 'PT1S',
2515
            ]);
2516
        $override->save();
2517
 
2518
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2519
        role_assign($role->id, $user->id, $systemcontext->id);
2520
 
2521
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
2522
        role_unassign($studentrole->id, $user->id, $coursecontext->id);
2523
 
2524
        // This context is not _fully _ expired.
2525
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2526
    }
2527
 
2528
    /**
2529
     * Test the is_context_expired functions when supplied with an unexpired course which has role overrides.
2530
     */
11 efrain 2531
    public function test_is_context_expired_course_expired_override_inverse_parent_not_assigned(): void {
1 efrain 2532
        global $DB;
2533
 
2534
        $this->resetAfterTest();
2535
 
2536
        $purposes = $this->setup_basics('P1Y', 'P1Y');
2537
 
2538
        $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
2539
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
2540
        $coursecontext = \context_course::instance($course->id);
2541
        $systemcontext = \context_system::instance();
2542
 
2543
        $role = $DB->get_record('role', ['shortname' => 'manager']);
2544
        $override = new purpose_override(0, (object) [
2545
                'purposeid' => $purposes->system->get('id'),
2546
                'roleid' => $role->id,
2547
                'retentionperiod' => 'PT1S',
2548
            ]);
2549
        $override->save();
2550
 
2551
        // Enrol the user in the course without any role.
2552
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2553
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
2554
        role_unassign($studentrole->id, $user->id, $coursecontext->id);
2555
 
2556
        // This context is not _fully _ expired.
2557
        $this->assertFalse(expired_contexts_manager::is_context_expired($coursecontext));
2558
    }
2559
 
2560
    /**
2561
     * Ensure that context expired checks for a specific user taken into account roles.
2562
     */
11 efrain 2563
    public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected(): void {
1 efrain 2564
        global $DB;
2565
 
2566
        $this->resetAfterTest();
2567
 
2568
        $purposes = $this->setup_basics('PT1S', 'PT1S', 'PT1S');
2569
 
2570
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]);
2571
        $coursecontext = \context_course::instance($course->id);
2572
        $systemcontext = \context_system::instance();
2573
 
2574
        $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id');
2575
        $override = new purpose_override(0, (object) [
2576
                'purposeid' => $purposes->course->get('id'),
2577
                'roleid' => $roles['manager'],
2578
                'retentionperiod' => 'P1W',
2579
                'protected' => 1,
2580
            ]);
2581
        $override->save();
2582
 
2583
        $s = $this->getDataGenerator()->create_user();
2584
        $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student');
2585
 
2586
        $t = $this->getDataGenerator()->create_user();
2587
        $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
2588
 
2589
        $sm = $this->getDataGenerator()->create_user();
2590
        $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student');
2591
        role_assign($roles['manager'], $sm->id, $coursecontext->id);
2592
 
2593
        $m = $this->getDataGenerator()->create_user();
2594
        role_assign($roles['manager'], $m->id, $coursecontext->id);
2595
 
2596
        $tm = $this->getDataGenerator()->create_user();
2597
        $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
2598
        role_assign($roles['manager'], $tm->id, $coursecontext->id);
2599
 
2600
        // The context should only be expired for users who are not a manager.
2601
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
2602
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
2603
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
2604
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
2605
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
2606
 
2607
        $override->set('protected', 0)->save();
2608
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
2609
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
2610
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
2611
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
2612
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
2613
    }
2614
 
2615
    /**
2616
     * Ensure that context expired checks for a specific user taken into account roles when retention is inversed.
2617
     */
11 efrain 2618
    public function test_is_context_expired_or_unprotected_for_user_role_mixtures_protected_inverse(): void {
1 efrain 2619
        global $DB;
2620
 
2621
        $this->resetAfterTest();
2622
 
2623
        $purposes = $this->setup_basics('P5Y', 'P5Y', 'P5Y');
2624
 
2625
        $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - DAYSECS]);
2626
        $coursecontext = \context_course::instance($course->id);
2627
        $systemcontext = \context_system::instance();
2628
 
2629
        $roles = $DB->get_records_menu('role', [], 'id', 'shortname, id');
2630
        $override = new purpose_override(0, (object) [
2631
                'purposeid' => $purposes->course->get('id'),
2632
                'roleid' => $roles['student'],
2633
                'retentionperiod' => 'PT1S',
2634
            ]);
2635
        $override->save();
2636
 
2637
        $s = $this->getDataGenerator()->create_user();
2638
        $this->getDataGenerator()->enrol_user($s->id, $course->id, 'student');
2639
 
2640
        $t = $this->getDataGenerator()->create_user();
2641
        $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
2642
 
2643
        $sm = $this->getDataGenerator()->create_user();
2644
        $this->getDataGenerator()->enrol_user($sm->id, $course->id, 'student');
2645
        role_assign($roles['manager'], $sm->id, $coursecontext->id);
2646
 
2647
        $m = $this->getDataGenerator()->create_user();
2648
        role_assign($roles['manager'], $m->id, $coursecontext->id);
2649
 
2650
        $tm = $this->getDataGenerator()->create_user();
2651
        $this->getDataGenerator()->enrol_user($t->id, $course->id, 'teacher');
2652
        role_assign($roles['manager'], $tm->id, $coursecontext->id);
2653
 
2654
        // The context should only be expired for users who are only a student.
2655
        $purposes->course->set('protected', 1)->save();
2656
        $override->set('protected', 1)->save();
2657
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
2658
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
2659
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
2660
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
2661
        $this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
2662
 
2663
        $purposes->course->set('protected', 0)->save();
2664
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $s));
2665
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $t));
2666
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $sm));
2667
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $tm));
2668
        $this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($coursecontext, $m));
2669
    }
2670
}