Proyectos de Subversion Moodle

Rev

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