Proyectos de Subversion Moodle

Rev

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

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Tests for the forum implementation of the Privacy Provider API.
19
 *
20
 * @package    mod_forum
21
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace mod_forum\privacy;
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
global $CFG;
29
 
30
require_once(__DIR__ . '/../generator_trait.php');
31
require_once($CFG->dirroot . '/rating/lib.php');
32
 
33
use mod_forum\privacy\provider;
34
 
35
/**
36
 * Tests for the forum implementation of the Privacy Provider API.
37
 *
38
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 */
1441 ariadna 41
final class provider_test extends \core_privacy\tests\provider_testcase {
1 efrain 42
 
43
    // Include the privacy subcontext_info trait.
44
    // This includes the subcontext builders.
45
    use \mod_forum\privacy\subcontext_info;
46
 
47
    // Include the mod_forum test helpers.
48
    // This includes functions to create forums, users, discussions, and posts.
49
    use \mod_forum_tests_generator_trait;
50
 
51
    // Include the privacy helper trait for the ratings API.
52
    use \core_rating\phpunit\privacy_helper;
53
 
54
    // Include the privacy helper trait for the tag API.
55
    use \core_tag\tests\privacy_helper;
56
 
57
    /**
58
     * Test setUp.
59
     */
60
    public function setUp(): void {
1441 ariadna 61
        parent::setUp();
1 efrain 62
        $this->resetAfterTest(true);
63
    }
64
 
65
    /**
66
     * Helper to assert that the forum data is correct.
67
     *
68
     * @param   object  $expected The expected data in the forum.
69
     * @param   object  $actual The actual data in the forum.
70
     */
71
    protected function assert_forum_data($expected, $actual) {
72
        // Exact matches.
73
        $this->assertEquals(format_string($expected->name, true), $actual->name);
74
    }
75
 
76
    /**
77
     * Helper to assert that the discussion data is correct.
78
     *
79
     * @param   object  $expected The expected data in the discussion.
80
     * @param   object  $actual The actual data in the discussion.
81
     */
82
    protected function assert_discussion_data($expected, $actual) {
83
        // Exact matches.
84
        $this->assertEquals(format_string($expected->name, true), $actual->name);
85
        $this->assertEquals(
86
            \core_privacy\local\request\transform::yesno($expected->pinned),
87
            $actual->pinned
88
        );
89
 
90
        $this->assertEquals(
91
            \core_privacy\local\request\transform::datetime($expected->timemodified),
92
            $actual->timemodified
93
        );
94
 
95
        $this->assertEquals(
96
            \core_privacy\local\request\transform::datetime($expected->usermodified),
97
            $actual->usermodified
98
        );
99
    }
100
 
101
    /**
102
     * Helper to assert that the post data is correct.
103
     *
104
     * @param   object  $expected The expected data in the post.
105
     * @param   object  $actual The actual data in the post.
106
     * @param   \core_privacy\local\request\writer  $writer The writer used
107
     */
108
    protected function assert_post_data($expected, $actual, $writer) {
109
        // Exact matches.
110
        $this->assertEquals(format_string($expected->subject, true), $actual->subject);
111
 
112
        // The message should have been passed through the rewriter.
113
        // Note: The testable rewrite_pluginfile_urls function in the ignores all items except the text.
114
        $this->assertEquals(
115
            $writer->rewrite_pluginfile_urls([], '', '', '', $expected->message),
116
            $actual->message
117
        );
118
 
119
        $this->assertEquals(
120
            \core_privacy\local\request\transform::datetime($expected->created),
121
            $actual->created
122
        );
123
 
124
        $this->assertEquals(
125
            \core_privacy\local\request\transform::datetime($expected->modified),
126
            $actual->modified
127
        );
128
    }
129
 
130
    /**
131
     * Test that a user who is enrolled in a course, but who has never
132
     * posted and has no other metadata stored will not have any link to
133
     * that context.
134
     */
11 efrain 135
    public function test_user_has_never_posted(): void {
1 efrain 136
        // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
137
        $course = $this->getDataGenerator()->create_course();
138
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
139
        $course = $this->getDataGenerator()->create_course();
140
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
141
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
142
        list($user, $otheruser) = $this->helper_create_users($course, 2);
143
        list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
144
        $cm = get_coursemodule_from_instance('forum', $forum->id);
145
        $context = \context_module::instance($cm->id);
146
 
147
        // Test that no contexts were retrieved.
148
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
149
        $contexts = $contextlist->get_contextids();
150
        $this->assertCount(0, $contexts);
151
 
152
        // Attempting to export data for this context should return nothing either.
153
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
154
 
155
        $writer = \core_privacy\local\request\writer::with_context($context);
156
 
157
        // The provider should always export data for any context explicitly asked of it, but there should be no
158
        // metadata, files, or discussions.
159
        $this->assertEmpty($writer->get_data([get_string('discussions', 'mod_forum')]));
160
        $this->assertEmpty($writer->get_all_metadata([]));
161
        $this->assertEmpty($writer->get_files([]));
162
    }
163
 
164
    /**
165
     * Test that a user who is enrolled in a course, and who has never
166
     * posted and has subscribed to the forum will have relevant
167
     * information returned.
168
     */
11 efrain 169
    public function test_user_has_never_posted_subscribed_to_forum(): void {
1 efrain 170
        global $DB;
171
 
172
        // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
173
        $course = $this->getDataGenerator()->create_course();
174
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
175
        $course = $this->getDataGenerator()->create_course();
176
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
177
        $course = $this->getDataGenerator()->create_course();
178
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
179
        list($user, $otheruser) = $this->helper_create_users($course, 2);
180
        list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
181
        $cm = get_coursemodule_from_instance('forum', $forum->id);
182
        $context = \context_module::instance($cm->id);
183
 
184
        // Subscribe the user to the forum.
185
        \mod_forum\subscriptions::subscribe_user($user->id, $forum);
186
 
187
        // Retrieve all contexts - only this context should be returned.
188
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
189
        $this->assertCount(1, $contextlist);
190
        $this->assertEquals($context, $contextlist->current());
191
 
192
        // Export all of the data for the context.
193
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
194
        $writer = \core_privacy\local\request\writer::with_context($context);
195
        $this->assertTrue($writer->has_any_data());
196
 
197
        $subcontext = $this->get_subcontext($forum);
198
        // There should be one item of metadata.
199
        $this->assertCount(1, $writer->get_all_metadata($subcontext));
200
 
201
        // It should be the subscriptionpreference whose value is 1.
202
        $this->assertEquals(1, $writer->get_metadata($subcontext, 'subscriptionpreference'));
203
 
204
        // There should be data about the forum itself.
205
        $this->assertNotEmpty($writer->get_data($subcontext));
206
 
207
        // Delete the data now.
208
        // Only the post by the user under test will be removed.
209
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
210
            \core_user::get_user($user->id),
211
            'mod_forum',
212
            [$context->id]
213
        );
214
        $this->assertCount(1, $DB->get_records('forum_subscriptions', ['userid' => $user->id]));
215
        provider::delete_data_for_user($approvedcontextlist);
216
        $this->assertCount(0, $DB->get_records('forum_subscriptions', ['userid' => $user->id]));
217
    }
218
 
219
    /**
220
     * Test that a user who is enrolled in a course, and who has never
221
     * posted and has subscribed to the discussion will have relevant
222
     * information returned.
223
     */
11 efrain 224
    public function test_user_has_never_posted_subscribed_to_discussion(): void {
1 efrain 225
        global $DB;
226
 
227
        // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
228
        $course = $this->getDataGenerator()->create_course();
229
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
230
        $course = $this->getDataGenerator()->create_course();
231
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
232
        $course = $this->getDataGenerator()->create_course();
233
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
234
        list($user, $otheruser) = $this->helper_create_users($course, 2);
235
        // Post twice - only the second discussion should be included.
236
        $this->helper_post_to_forum($forum, $otheruser);
237
        list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
238
        $cm = get_coursemodule_from_instance('forum', $forum->id);
239
        $context = \context_module::instance($cm->id);
240
 
241
        // Subscribe the user to the discussion.
242
        \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion);
243
 
244
        // Retrieve all contexts - only this context should be returned.
245
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
246
        $this->assertCount(1, $contextlist);
247
        $this->assertEquals($context, $contextlist->current());
248
 
249
        // Export all of the data for the context.
250
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
251
        $writer = \core_privacy\local\request\writer::with_context($context);
252
        $this->assertTrue($writer->has_any_data());
253
 
254
        // There should be nothing in the forum. The user is not subscribed there.
255
        $forumsubcontext = $this->get_subcontext($forum);
256
        $this->assertCount(0, $writer->get_all_metadata($forumsubcontext));
257
        $this->assert_forum_data($forum, $writer->get_data($forumsubcontext));
258
 
259
        // There should be metadata in the discussion.
260
        $discsubcontext = $this->get_subcontext($forum, $discussion);
261
        $this->assertCount(1, $writer->get_all_metadata($discsubcontext));
262
 
263
        // It should be the subscriptionpreference whose value is an Integer.
264
        // (It's a timestamp, but it doesn't matter).
265
        $metadata = $writer->get_metadata($discsubcontext, 'subscriptionpreference');
266
        $this->assertGreaterThan(1, $metadata);
267
 
268
        // For context we output the discussion content.
269
        $data = $writer->get_data($discsubcontext);
270
        $this->assertInstanceOf('stdClass', $data);
271
        $this->assert_discussion_data($discussion, $data);
272
 
273
        // Post content is not exported unless the user participated.
274
        $postsubcontext = $this->get_subcontext($forum, $discussion, $post);
275
        $this->assertCount(0, $writer->get_data($postsubcontext));
276
 
277
        // Delete the data now.
278
        // Only the post by the user under test will be removed.
279
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
280
            \core_user::get_user($user->id),
281
            'mod_forum',
282
            [$context->id]
283
        );
284
        $this->assertCount(1, $DB->get_records('forum_discussion_subs', ['userid' => $user->id]));
285
        provider::delete_data_for_user($approvedcontextlist);
286
        $this->assertCount(0, $DB->get_records('forum_discussion_subs', ['userid' => $user->id]));
287
    }
288
 
289
    /**
290
     * Test that a user who has posted their own discussion will have all
291
     * content returned.
292
     */
11 efrain 293
    public function test_user_has_posted_own_discussion(): void {
1 efrain 294
        $course = $this->getDataGenerator()->create_course();
295
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
296
        $course = $this->getDataGenerator()->create_course();
297
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
298
        $course = $this->getDataGenerator()->create_course();
299
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
300
        list($user, $otheruser) = $this->helper_create_users($course, 2);
301
 
302
        // Post twice - only the second discussion should be included.
303
        list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
304
        list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $otheruser);
305
        $cm = get_coursemodule_from_instance('forum', $forum->id);
306
        $context = \context_module::instance($cm->id);
307
 
308
        // Retrieve all contexts - only this context should be returned.
309
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
310
        $this->assertCount(1, $contextlist);
311
        $this->assertEquals($context, $contextlist->current());
312
 
313
        // Export all of the data for the context.
314
        $this->setUser($user);
315
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
316
        $writer = \core_privacy\local\request\writer::with_context($context);
317
        $this->assertTrue($writer->has_any_data());
318
 
319
        // The other discussion should not have been returned as we did not post in it.
320
        $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $otherdiscussion)));
321
 
322
        $this->assert_discussion_data($discussion, $writer->get_data($this->get_subcontext($forum, $discussion)));
323
        $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
324
    }
325
 
326
    /**
327
     * Test that a user who has posted a reply to another users discussion will have all content returned, and
328
     * appropriate content removed.
329
     */
11 efrain 330
    public function test_user_has_posted_reply(): void {
1 efrain 331
        global $DB;
332
 
333
        // Create several courses and forums. We only insert data into the final one.
334
        $course = $this->getDataGenerator()->create_course();
335
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
336
        $course = $this->getDataGenerator()->create_course();
337
        $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
338
 
339
        $course = $this->getDataGenerator()->create_course();
340
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
341
        list($user, $otheruser) = $this->helper_create_users($course, 2);
342
        // Post twice - only the second discussion should be included.
343
        list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
344
        list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $otheruser);
345
        $cm = get_coursemodule_from_instance('forum', $forum->id);
346
        $context = \context_module::instance($cm->id);
347
 
348
        // Post a reply to the other person's post.
349
        $reply = $this->helper_reply_to_post($post, $user);
350
 
351
        // Testing as user $user.
352
        $this->setUser($user);
353
 
354
        // Retrieve all contexts - only this context should be returned.
355
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
356
        $this->assertCount(1, $contextlist);
357
        $this->assertEquals($context, $contextlist->current());
358
 
359
        // Export all of the data for the context.
360
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
361
        $writer = \core_privacy\local\request\writer::with_context($context);
362
        $this->assertTrue($writer->has_any_data());
363
 
364
        // Refresh the discussions.
365
        $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
366
        $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
367
 
368
        // The other discussion should not have been returned as we did not post in it.
369
        $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $otherdiscussion)));
370
 
371
        // Our discussion should have been returned as we did post in it.
372
        $data = $writer->get_data($this->get_subcontext($forum, $discussion));
373
        $this->assertNotEmpty($data);
374
        $this->assert_discussion_data($discussion, $data);
375
 
376
        // The reply will be included.
377
        $this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
378
 
379
        // Delete the data now.
380
        // Only the post by the user under test will be removed.
381
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
382
            \core_user::get_user($user->id),
383
            'mod_forum',
384
            [$context->id]
385
        );
386
        provider::delete_data_for_user($approvedcontextlist);
387
 
388
        $reply = $DB->get_record('forum_posts', ['id' => $reply->id]);
389
        $this->assertEmpty($reply->subject);
390
        $this->assertEmpty($reply->message);
391
        $this->assertEquals(1, $reply->deleted);
392
 
393
        $post = $DB->get_record('forum_posts', ['id' => $post->id]);
394
        $this->assertNotEmpty($post->subject);
395
        $this->assertNotEmpty($post->message);
396
        $this->assertEquals(0, $post->deleted);
397
    }
398
 
399
    /**
400
     * Test private reply in a range of scenarios.
401
     */
11 efrain 402
    public function test_user_private_reply(): void {
1 efrain 403
        global $DB;
404
 
405
        $course = $this->getDataGenerator()->create_course();
406
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
407
        $cm = get_coursemodule_from_instance('forum', $forum->id);
408
        $context = \context_module::instance($cm->id);
409
 
410
        [$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
411
        [$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
412
 
413
        [$discussion, $post] = $this->helper_post_to_forum($forum, $student);
414
        $reply = $this->helper_reply_to_post($post, $teacher, [
415
                'privatereplyto' => $student->id,
416
            ]);
417
 
418
        // Testing as user $student.
419
        $this->setUser($student);
420
 
421
        // Retrieve all contexts - only this context should be returned.
422
        $contextlist = $this->get_contexts_for_userid($student->id, 'mod_forum');
423
        $this->assertCount(1, $contextlist);
424
        $this->assertEquals($context, $contextlist->current());
425
 
426
        // Export all of the data for the context.
427
        $this->export_context_data_for_user($student->id, $context, 'mod_forum');
428
        $writer = \core_privacy\local\request\writer::with_context($context);
429
        $this->assertTrue($writer->has_any_data());
430
 
431
        // The initial post and reply will be included.
432
        $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
433
        $this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
434
 
435
        // Testing as user $teacher.
436
        \core_privacy\local\request\writer::reset();
437
        $this->setUser($teacher);
438
 
439
        // Retrieve all contexts - only this context should be returned.
440
        $contextlist = $this->get_contexts_for_userid($teacher->id, 'mod_forum');
441
        $this->assertCount(1, $contextlist);
442
        $this->assertEquals($context, $contextlist->current());
443
 
444
        // Export all of the data for the context.
445
        $this->export_context_data_for_user($teacher->id, $context, 'mod_forum');
446
        $writer = \core_privacy\local\request\writer::with_context($context);
447
        $this->assertTrue($writer->has_any_data());
448
 
449
        // The reply will be included.
450
        $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
451
        $this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
452
 
453
        // Testing as user $otherteacher.
454
        // The user was not involved in any of the conversation.
455
        \core_privacy\local\request\writer::reset();
456
        $this->setUser($otherteacher);
457
 
458
        // Retrieve all contexts - only this context should be returned.
459
        $contextlist = $this->get_contexts_for_userid($otherteacher->id, 'mod_forum');
460
        $this->assertCount(0, $contextlist);
461
 
462
        // Export all of the data for the context.
463
        $this->export_context_data_for_user($otherteacher->id, $context, 'mod_forum');
464
        $writer = \core_privacy\local\request\writer::with_context($context);
465
 
466
        // The user has none of the discussion.
467
        $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
468
 
469
        // Testing as user $otherstudent.
470
        // The user was not involved in any of the conversation.
471
        \core_privacy\local\request\writer::reset();
472
        $this->setUser($otherstudent);
473
 
474
        // Retrieve all contexts - only this context should be returned.
475
        $contextlist = $this->get_contexts_for_userid($otherstudent->id, 'mod_forum');
476
        $this->assertCount(0, $contextlist);
477
 
478
        // Export all of the data for the context.
479
        $this->export_context_data_for_user($otherstudent->id, $context, 'mod_forum');
480
        $writer = \core_privacy\local\request\writer::with_context($context);
481
 
482
        // The user has none of the discussion.
483
        $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
484
    }
485
 
486
    /**
487
     * Test that the rating of another users content will have only the
488
     * rater's information returned.
489
     */
11 efrain 490
    public function test_user_has_rated_others(): void {
1 efrain 491
        global $DB;
492
 
493
        $course = $this->getDataGenerator()->create_course();
494
        $forum = $this->getDataGenerator()->create_module('forum', [
495
            'course' => $course->id,
496
            'scale' => 100,
497
        ]);
498
        list($user, $otheruser) = $this->helper_create_users($course, 2);
499
        list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
500
        $cm = get_coursemodule_from_instance('forum', $forum->id);
501
        $context = \context_module::instance($cm->id);
502
 
503
        // Rate the other users content.
504
        $rm = new \rating_manager();
505
        $ratingoptions = new \stdClass;
506
        $ratingoptions->context = $context;
507
        $ratingoptions->component = 'mod_forum';
508
        $ratingoptions->ratingarea = 'post';
509
        $ratingoptions->itemid  = $post->id;
510
        $ratingoptions->scaleid = $forum->scale;
511
        $ratingoptions->userid  = $user->id;
512
 
513
        $rating = new \rating($ratingoptions);
514
        $rating->update_rating(75);
515
 
516
        // Run as the user under test.
517
        $this->setUser($user);
518
 
519
        // Retrieve all contexts - only this context should be returned.
520
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
521
        $this->assertCount(1, $contextlist);
522
        $this->assertEquals($context, $contextlist->current());
523
 
524
        // Export all of the data for the context.
525
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
526
        $writer = \core_privacy\local\request\writer::with_context($context);
527
        $this->assertTrue($writer->has_any_data());
528
 
529
        // The discussion should not have been returned as we did not post in it.
530
        $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
531
 
532
        $this->assert_all_own_ratings_on_context(
533
            $user->id,
534
            $context,
535
            $this->get_subcontext($forum, $discussion, $post),
536
            'mod_forum',
537
            'post',
538
            $post->id
539
        );
540
 
541
        // The original post will not be included.
542
        $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
543
 
544
        // Delete the data of the user who rated the other user.
545
        // The rating should not be deleted as it the rating is considered grading data.
546
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
547
            \core_user::get_user($user->id),
548
            'mod_forum',
549
            [$context->id]
550
        );
551
        provider::delete_data_for_user($approvedcontextlist);
552
 
553
        // Ratings should remain as they are of another user's content.
554
        $this->assertCount(1, $DB->get_records('rating', ['itemid' => $post->id]));
555
    }
556
 
557
    /**
558
     * Test that ratings of a users own content will all be returned.
559
     */
11 efrain 560
    public function test_user_has_been_rated(): void {
1 efrain 561
        global $DB;
562
 
563
        $course = $this->getDataGenerator()->create_course();
564
        $forum = $this->getDataGenerator()->create_module('forum', [
565
            'course' => $course->id,
566
            'scale' => 100,
567
        ]);
568
        list($user, $otheruser, $anotheruser) = $this->helper_create_users($course, 3);
569
        list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
570
        $cm = get_coursemodule_from_instance('forum', $forum->id);
571
        $context = \context_module::instance($cm->id);
572
 
573
        // Other users rate my content.
574
        $rm = new \rating_manager();
575
        $ratingoptions = new \stdClass;
576
        $ratingoptions->context = $context;
577
        $ratingoptions->component = 'mod_forum';
578
        $ratingoptions->ratingarea = 'post';
579
        $ratingoptions->itemid  = $post->id;
580
        $ratingoptions->scaleid = $forum->scale;
581
 
582
        $ratingoptions->userid  = $otheruser->id;
583
        $rating = new \rating($ratingoptions);
584
        $rating->update_rating(75);
585
 
586
        $ratingoptions->userid  = $anotheruser->id;
587
        $rating = new \rating($ratingoptions);
588
        $rating->update_rating(75);
589
 
590
        // Run as the user under test.
591
        $this->setUser($user);
592
 
593
        // Retrieve all contexts - only this context should be returned.
594
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
595
        $this->assertCount(1, $contextlist);
596
        $this->assertEquals($context, $contextlist->current());
597
 
598
        // Export all of the data for the context.
599
        $this->export_context_data_for_user($user->id, $context, 'mod_forum');
600
        $writer = \core_privacy\local\request\writer::with_context($context);
601
        $this->assertTrue($writer->has_any_data());
602
 
603
        $this->assert_all_ratings_on_context(
604
            $context,
605
            $this->get_subcontext($forum, $discussion, $post),
606
            'mod_forum',
607
            'post',
608
            $post->id
609
        );
610
 
611
        // Delete the data of the user who was rated.
612
        // The rating should now be deleted.
613
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
614
            \core_user::get_user($user->id),
615
            'mod_forum',
616
            [$context->id]
617
        );
618
        provider::delete_data_for_user($approvedcontextlist);
619
 
620
        // Ratings should remain as they are of another user's content.
621
        $this->assertCount(0, $DB->get_records('rating', ['itemid' => $post->id]));
622
    }
623
 
624
    /**
625
     * Test that per-user daily digest settings are included correctly.
626
     */
11 efrain 627
    public function test_user_forum_digest(): void {
1 efrain 628
        global $DB;
629
 
630
        $course = $this->getDataGenerator()->create_course();
631
 
632
        $forum0 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
633
        $cm0 = get_coursemodule_from_instance('forum', $forum0->id);
634
        $context0 = \context_module::instance($cm0->id);
635
 
636
        $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
637
        $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
638
        $context1 = \context_module::instance($cm1->id);
639
 
640
        $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
641
        $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
642
        $context2 = \context_module::instance($cm2->id);
643
 
644
        $forum3 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
645
        $cm3 = get_coursemodule_from_instance('forum', $forum3->id);
646
        $context3 = \context_module::instance($cm3->id);
647
 
648
        list($user) = $this->helper_create_users($course, 1);
649
 
650
        // Set a digest value for each forum.
651
        forum_set_user_maildigest($forum0, 0, $user);
652
        forum_set_user_maildigest($forum1, 1, $user);
653
        forum_set_user_maildigest($forum2, 2, $user);
654
 
655
        // Run as the user under test.
656
        $this->setUser($user);
657
 
658
        // Retrieve all contexts - three contexts should be returned - the fourth should not be included.
659
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
660
        $this->assertCount(3, $contextlist);
661
 
662
        $contextids = [
663
                $context0->id,
664
                $context1->id,
665
                $context2->id,
666
            ];
667
        sort($contextids);
668
        $contextlistids = $contextlist->get_contextids();
669
        sort($contextlistids);
670
        $this->assertEquals($contextids, $contextlistids);
671
 
672
        // Check export data for each context.
673
        $this->export_context_data_for_user($user->id, $context0, 'mod_forum');
674
        $this->assertEquals(0, \core_privacy\local\request\writer::with_context($context0)->get_metadata([], 'digestpreference'));
675
 
676
        $this->export_context_data_for_user($user->id, $context1, 'mod_forum');
677
        $this->assertEquals(1, \core_privacy\local\request\writer::with_context($context1)->get_metadata([], 'digestpreference'));
678
 
679
        $this->export_context_data_for_user($user->id, $context2, 'mod_forum');
680
        $this->assertEquals(2, \core_privacy\local\request\writer::with_context($context2)->get_metadata([], 'digestpreference'));
681
 
682
        // Delete the data for one of the users in one of the forums.
683
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
684
            \core_user::get_user($user->id),
685
            'mod_forum',
686
            [$context1->id]
687
        );
688
 
689
        $this->assertEquals(0, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum0->id]));
690
        $this->assertEquals(1, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum1->id]));
691
        $this->assertEquals(2, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum2->id]));
692
        provider::delete_data_for_user($approvedcontextlist);
693
        $this->assertEquals(0, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum0->id]));
694
        $this->assertFalse($DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum1->id]));
695
        $this->assertEquals(2, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum2->id]));
696
 
697
    }
698
 
699
    /**
700
     * Test that the per-user, per-forum user tracking data is exported.
701
     */
11 efrain 702
    public function test_user_tracking_data(): void {
1 efrain 703
        global $DB;
704
 
705
        $course = $this->getDataGenerator()->create_course();
706
 
707
        $forumoff = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
708
        $cmoff = get_coursemodule_from_instance('forum', $forumoff->id);
709
        $contextoff = \context_module::instance($cmoff->id);
710
 
711
        $forumon = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
712
        $cmon = get_coursemodule_from_instance('forum', $forumon->id);
713
        $contexton = \context_module::instance($cmon->id);
714
 
715
        list($user) = $this->helper_create_users($course, 1);
716
 
717
        // Set user tracking data.
718
        forum_tp_stop_tracking($forumoff->id, $user->id);
719
        forum_tp_start_tracking($forumon->id, $user->id);
720
 
721
        // Run as the user under test.
722
        $this->setUser($user);
723
 
724
        // Retrieve all contexts - only the forum tracking reads should be included.
725
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
726
        $this->assertCount(1, $contextlist);
727
        $this->assertEquals($contextoff, $contextlist->current());
728
 
729
        // Check export data for each context.
730
        $this->export_context_data_for_user($user->id, $contextoff, 'mod_forum');
731
        $this->assertEquals(0,
732
                \core_privacy\local\request\writer::with_context($contextoff)->get_metadata([], 'trackreadpreference'));
733
 
734
        // Delete the data for one of the users in the 'on' forum.
735
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
736
            \core_user::get_user($user->id),
737
            'mod_forum',
738
            [$contexton->id]
739
        );
740
 
741
        $this->assertTrue($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
742
        $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
743
 
744
        provider::delete_data_for_user($approvedcontextlist);
745
 
746
        $this->assertTrue($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
747
        $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
748
 
749
        // Delete the data for one of the users in the 'off' forum.
750
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
751
            \core_user::get_user($user->id),
752
            'mod_forum',
753
            [$contextoff->id]
754
        );
755
 
756
        provider::delete_data_for_user($approvedcontextlist);
757
 
758
        $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
759
        $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
760
    }
761
 
762
    /**
763
     * Test that the posts which a user has read are returned correctly.
764
     */
11 efrain 765
    public function test_user_read_posts(): void {
1 efrain 766
        global $DB;
767
 
768
        $course = $this->getDataGenerator()->create_course();
769
 
770
        $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
771
        $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
772
        $context1 = \context_module::instance($cm1->id);
773
 
774
        $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
775
        $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
776
        $context2 = \context_module::instance($cm2->id);
777
 
778
        $forum3 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
779
        $cm3 = get_coursemodule_from_instance('forum', $forum3->id);
780
        $context3 = \context_module::instance($cm3->id);
781
 
782
        $forum4 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
783
        $cm4 = get_coursemodule_from_instance('forum', $forum4->id);
784
        $context4 = \context_module::instance($cm4->id);
785
 
786
        list($author, $user) = $this->helper_create_users($course, 2);
787
 
788
        list($f1d1, $f1p1) = $this->helper_post_to_forum($forum1, $author);
789
        $f1p1reply = $this->helper_post_to_discussion($forum1, $f1d1, $author);
790
        $f1d1 = $DB->get_record('forum_discussions', ['id' => $f1d1->id]);
791
        list($f1d2, $f1p2) = $this->helper_post_to_forum($forum1, $author);
792
 
793
        list($f2d1, $f2p1) = $this->helper_post_to_forum($forum2, $author);
794
        $f2p1reply = $this->helper_post_to_discussion($forum2, $f2d1, $author);
795
        $f2d1 = $DB->get_record('forum_discussions', ['id' => $f2d1->id]);
796
        list($f2d2, $f2p2) = $this->helper_post_to_forum($forum2, $author);
797
 
798
        list($f3d1, $f3p1) = $this->helper_post_to_forum($forum3, $author);
799
        $f3p1reply = $this->helper_post_to_discussion($forum3, $f3d1, $author);
800
        $f3d1 = $DB->get_record('forum_discussions', ['id' => $f3d1->id]);
801
        list($f3d2, $f3p2) = $this->helper_post_to_forum($forum3, $author);
802
 
803
        list($f4d1, $f4p1) = $this->helper_post_to_forum($forum4, $author);
804
        $f4p1reply = $this->helper_post_to_discussion($forum4, $f4d1, $author);
805
        $f4d1 = $DB->get_record('forum_discussions', ['id' => $f4d1->id]);
806
        list($f4d2, $f4p2) = $this->helper_post_to_forum($forum4, $author);
807
 
808
        // Insert read info.
809
        // User has read post1, but not the reply or second post in forum1.
810
        forum_tp_add_read_record($user->id, $f1p1->id);
811
 
812
        // User has read post1 and its reply, but not the second post in forum2.
813
        forum_tp_add_read_record($user->id, $f2p1->id);
814
        forum_tp_add_read_record($user->id, $f2p1reply->id);
815
 
816
        // User has read post2 in forum3.
817
        forum_tp_add_read_record($user->id, $f3p2->id);
818
 
819
        // Nothing has been read in forum4.
820
 
821
        // Run as the user under test.
822
        $this->setUser($user);
823
 
824
        // Retrieve all contexts - should be three - forum4 has no data.
825
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
826
        $this->assertCount(3, $contextlist);
827
 
828
        $contextids = [
829
                $context1->id,
830
                $context2->id,
831
                $context3->id,
832
            ];
833
        sort($contextids);
834
        $contextlistids = $contextlist->get_contextids();
835
        sort($contextlistids);
836
        $this->assertEquals($contextids, $contextlistids);
837
 
838
        // Forum 1.
839
        $this->export_context_data_for_user($user->id, $context1, 'mod_forum');
840
        $writer = \core_privacy\local\request\writer::with_context($context1);
841
 
842
        // User has read f1p1.
843
        $readdata = $writer->get_metadata(
844
                $this->get_subcontext($forum1, $f1d1, $f1p1),
845
                'postread'
846
            );
847
        $this->assertNotEmpty($readdata);
848
        $this->assertTrue(isset($readdata->firstread));
849
        $this->assertTrue(isset($readdata->lastread));
850
 
851
        // User has not f1p1reply.
852
        $readdata = $writer->get_metadata(
853
                $this->get_subcontext($forum1, $f1d1, $f1p1reply),
854
                'postread'
855
            );
856
        $this->assertEmpty($readdata);
857
 
858
        // User has not f1p2.
859
        $readdata = $writer->get_metadata(
860
                $this->get_subcontext($forum1, $f1d2, $f1p2),
861
                'postread'
862
            );
863
        $this->assertEmpty($readdata);
864
 
865
        // Forum 2.
866
        $this->export_context_data_for_user($user->id, $context2, 'mod_forum');
867
        $writer = \core_privacy\local\request\writer::with_context($context2);
868
 
869
        // User has read f2p1.
870
        $readdata = $writer->get_metadata(
871
                $this->get_subcontext($forum2, $f2d1, $f2p1),
872
                'postread'
873
            );
874
        $this->assertNotEmpty($readdata);
875
        $this->assertTrue(isset($readdata->firstread));
876
        $this->assertTrue(isset($readdata->lastread));
877
 
878
        // User has read f2p1reply.
879
        $readdata = $writer->get_metadata(
880
                $this->get_subcontext($forum2, $f2d1, $f2p1reply),
881
                'postread'
882
            );
883
        $this->assertNotEmpty($readdata);
884
        $this->assertTrue(isset($readdata->firstread));
885
        $this->assertTrue(isset($readdata->lastread));
886
 
887
        // User has not read f2p2.
888
        $readdata = $writer->get_metadata(
889
                $this->get_subcontext($forum2, $f2d2, $f2p2),
890
                'postread'
891
            );
892
        $this->assertEmpty($readdata);
893
 
894
        // Forum 3.
895
        $this->export_context_data_for_user($user->id, $context3, 'mod_forum');
896
        $writer = \core_privacy\local\request\writer::with_context($context3);
897
 
898
        // User has not read f3p1.
899
        $readdata = $writer->get_metadata(
900
                $this->get_subcontext($forum3, $f3d1, $f3p1),
901
                'postread'
902
            );
903
        $this->assertEmpty($readdata);
904
 
905
        // User has not read f3p1reply.
906
        $readdata = $writer->get_metadata(
907
                $this->get_subcontext($forum3, $f3d1, $f3p1reply),
908
                'postread'
909
            );
910
        $this->assertEmpty($readdata);
911
 
912
        // User has read f3p2.
913
        $readdata = $writer->get_metadata(
914
                $this->get_subcontext($forum3, $f3d2, $f3p2),
915
                'postread'
916
            );
917
        $this->assertNotEmpty($readdata);
918
        $this->assertTrue(isset($readdata->firstread));
919
        $this->assertTrue(isset($readdata->lastread));
920
 
921
        // Delete all data for one of the users in one of the forums.
922
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
923
            \core_user::get_user($user->id),
924
            'mod_forum',
925
            [$context3->id]
926
        );
927
 
928
        $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum1->id]));
929
        $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum2->id]));
930
        $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum3->id]));
931
 
932
        provider::delete_data_for_user($approvedcontextlist);
933
 
934
        $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum1->id]));
935
        $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum2->id]));
936
        $this->assertFalse($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum3->id]));
937
    }
938
 
939
    /**
940
     * Test that posts with attachments have their attachments correctly exported.
941
     */
11 efrain 942
    public function test_post_attachment_inclusion(): void {
1 efrain 943
        global $DB;
944
 
945
        $fs = get_file_storage();
946
        $course = $this->getDataGenerator()->create_course();
947
        list($author, $otheruser) = $this->helper_create_users($course, 2);
948
 
949
        $forum = $this->getDataGenerator()->create_module('forum', [
950
            'course' => $course->id,
951
            'scale' => 100,
952
        ]);
953
        $cm = get_coursemodule_from_instance('forum', $forum->id);
954
        $context = \context_module::instance($cm->id);
955
 
956
        // Create a new discussion + post in the forum.
957
        list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
958
        $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
959
 
960
        // Add a number of replies.
961
        $reply = $this->helper_reply_to_post($post, $author);
962
        $reply = $this->helper_reply_to_post($post, $author);
963
        $reply = $this->helper_reply_to_post($reply, $author);
964
        $posts[$reply->id] = $reply;
965
 
966
        // Add a fake inline image to the original post.
967
        $createdfile = $fs->create_file_from_string([
968
                'contextid' => $context->id,
969
                'component' => 'mod_forum',
970
                'filearea'  => 'post',
971
                'itemid'    => $post->id,
972
                'filepath'  => '/',
973
                'filename'  => 'example.jpg',
974
            ],
975
        'image contents (not really)');
976
 
977
        // Tag the post and the final reply.
978
        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
979
        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $reply->id, $context, ['example', 'differenttag']);
980
 
981
        // Create a second discussion + post in the forum without tags.
982
        list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $author);
983
        $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
984
 
985
        // Add a number of replies.
986
        $reply = $this->helper_reply_to_post($otherpost, $author);
987
        $reply = $this->helper_reply_to_post($otherpost, $author);
988
 
989
        // Run as the user under test.
990
        $this->setUser($author);
991
 
992
        // Retrieve all contexts - should be one.
993
        $contextlist = $this->get_contexts_for_userid($author->id, 'mod_forum');
994
        $this->assertCount(1, $contextlist);
995
 
996
        $this->export_context_data_for_user($author->id, $context, 'mod_forum');
997
        $writer = \core_privacy\local\request\writer::with_context($context);
998
 
999
        // The inline file should be on the first forum post.
1000
        $subcontext = $this->get_subcontext($forum, $discussion, $post);
1001
        $foundfiles = $writer->get_files($subcontext);
1002
        $this->assertCount(1, $foundfiles);
1003
        $this->assertEquals($createdfile, reset($foundfiles));
1004
    }
1005
 
1006
    /**
1007
     * Test that posts which include tags have those tags exported.
1008
     */
11 efrain 1009
    public function test_post_tags(): void {
1 efrain 1010
        global $DB;
1011
 
1012
        $course = $this->getDataGenerator()->create_course();
1013
        list($author, $otheruser) = $this->helper_create_users($course, 2);
1014
 
1015
        $forum = $this->getDataGenerator()->create_module('forum', [
1016
            'course' => $course->id,
1017
            'scale' => 100,
1018
        ]);
1019
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1020
        $context = \context_module::instance($cm->id);
1021
 
1022
        // Create a new discussion + post in the forum.
1023
        list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
1024
        $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1025
 
1026
        // Add a number of replies.
1027
        $reply = $this->helper_reply_to_post($post, $author);
1028
        $reply = $this->helper_reply_to_post($post, $author);
1029
        $reply = $this->helper_reply_to_post($reply, $author);
1030
        $posts[$reply->id] = $reply;
1031
 
1032
        // Tag the post and the final reply.
1033
        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1034
        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $reply->id, $context, ['example', 'differenttag']);
1035
 
1036
        // Create a second discussion + post in the forum without tags.
1037
        list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $author);
1038
        $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
1039
 
1040
        // Add a number of replies.
1041
        $reply = $this->helper_reply_to_post($otherpost, $author);
1042
        $reply = $this->helper_reply_to_post($otherpost, $author);
1043
 
1044
        // Run as the user under test.
1045
        $this->setUser($author);
1046
 
1047
        // Retrieve all contexts - should be two.
1048
        $contextlist = $this->get_contexts_for_userid($author->id, 'mod_forum');
1049
        $this->assertCount(1, $contextlist);
1050
 
1051
        $this->export_all_data_for_user($author->id, 'mod_forum');
1052
        $writer = \core_privacy\local\request\writer::with_context($context);
1053
 
1054
        $this->assert_all_tags_match_on_context(
1055
            $author->id,
1056
            $context,
1057
            $this->get_subcontext($forum, $discussion, $post),
1058
            'mod_forum',
1059
            'forum_posts',
1060
            $post->id
1061
        );
1062
    }
1063
 
1064
    /**
1065
     * Ensure that all user data is deleted from a context.
1066
     */
11 efrain 1067
    public function test_all_users_deleted_from_context(): void {
1 efrain 1068
        global $DB;
1069
 
1070
        $fs = get_file_storage();
1071
        $course = $this->getDataGenerator()->create_course();
1072
        $users = $this->helper_create_users($course, 5);
1073
 
1074
        $forums = [];
1075
        $contexts = [];
1076
        for ($i = 0; $i < 2; $i++) {
1077
            $forum = $this->getDataGenerator()->create_module('forum', [
1078
                'course' => $course->id,
1079
                'scale' => 100,
1080
            ]);
1081
            $cm = get_coursemodule_from_instance('forum', $forum->id);
1082
            $context = \context_module::instance($cm->id);
1083
            $forums[$forum->id] = $forum;
1084
            $contexts[$forum->id] = $context;
1085
        }
1086
 
1087
        $discussions = [];
1088
        $posts = [];
1089
        foreach ($users as $user) {
1090
            foreach ($forums as $forum) {
1091
                $context = $contexts[$forum->id];
1092
 
1093
                // Create a new discussion + post in the forum.
1094
                list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1095
                $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1096
                $discussions[$discussion->id] = $discussion;
1097
 
1098
                // Add a number of replies.
1099
                $posts[$post->id] = $post;
1100
                $reply = $this->helper_reply_to_post($post, $user);
1101
                $posts[$reply->id] = $reply;
1102
                $reply = $this->helper_reply_to_post($post, $user);
1103
                $posts[$reply->id] = $reply;
1104
                $reply = $this->helper_reply_to_post($reply, $user);
1105
                $posts[$reply->id] = $reply;
1106
 
1107
                // Add a fake inline image to the original post.
1108
                $fs->create_file_from_string([
1109
                        'contextid' => $context->id,
1110
                        'component' => 'mod_forum',
1111
                        'filearea'  => 'post',
1112
                        'itemid'    => $post->id,
1113
                        'filepath'  => '/',
1114
                        'filename'  => 'example.jpg',
1115
                    ], 'image contents (not really)');
1116
                // And an attachment.
1117
                $fs->create_file_from_string([
1118
                        'contextid' => $context->id,
1119
                        'component' => 'mod_forum',
1120
                        'filearea'  => 'attachment',
1121
                        'itemid'    => $post->id,
1122
                        'filepath'  => '/',
1123
                        'filename'  => 'example.jpg',
1124
                    ], 'image contents (not really)');
1125
            }
1126
        }
1127
 
1128
        // Mark all posts as read by user.
1129
        $user = reset($users);
1130
        $ratedposts = [];
1131
        foreach ($posts as $post) {
1132
            $discussion = $discussions[$post->discussion];
1133
            $forum = $forums[$discussion->forum];
1134
            $context = $contexts[$forum->id];
1135
 
1136
            // Mark the post as being read by user.
1137
            forum_tp_add_read_record($user->id, $post->id);
1138
 
1139
            // Tag the post.
1140
            \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1141
 
1142
            // Rate the other users content.
1143
            if ($post->userid != $user->id) {
1144
                $ratedposts[$post->id] = $post;
1145
                $rm = new \rating_manager();
1146
                $ratingoptions = (object) [
1147
                    'context' => $context,
1148
                    'component' => 'mod_forum',
1149
                    'ratingarea' => 'post',
1150
                    'itemid' => $post->id,
1151
                    'scaleid' => $forum->scale,
1152
                    'userid' => $user->id,
1153
                ];
1154
 
1155
                $rating = new \rating($ratingoptions);
1156
                $rating->update_rating(75);
1157
            }
1158
        }
1159
 
1160
        // Run as the user under test.
1161
        $this->setUser($user);
1162
 
1163
        // Retrieve all contexts - should be two.
1164
        $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
1165
        $this->assertCount(2, $contextlist);
1166
 
1167
        // These are the contexts we expect.
1168
        $contextids = array_map(function($context) {
1169
            return $context->id;
1170
        }, $contexts);
1171
        sort($contextids);
1172
 
1173
        $contextlistids = $contextlist->get_contextids();
1174
        sort($contextlistids);
1175
        $this->assertEquals($contextids, $contextlistids);
1176
 
1177
        // Delete for the first forum.
1178
        $forum = reset($forums);
1179
        $context = $contexts[$forum->id];
1180
        provider::delete_data_for_all_users_in_context($context);
1181
 
1182
        // Determine what should have been deleted.
1183
        $discussionsinforum = array_filter($discussions, function($discussion) use ($forum) {
1184
            return $discussion->forum == $forum->id;
1185
        });
1186
 
1187
        $postsinforum = array_filter($posts, function($post) use ($discussionsinforum) {
1188
            return isset($discussionsinforum[$post->discussion]);
1189
        });
1190
 
1191
        // All forum discussions and posts should have been deleted in this forum.
1192
        $this->assertCount(0, $DB->get_records('forum_discussions', ['forum' => $forum->id]));
1193
 
1194
        list ($insql, $inparams) = $DB->get_in_or_equal(array_keys($discussionsinforum));
1195
        $this->assertCount(0, $DB->get_records_select('forum_posts', "discussion {$insql}", $inparams));
1196
 
1197
        // All uploaded files relating to this context should have been deleted (post content).
1198
        foreach ($postsinforum as $post) {
1199
            $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'post', $post->id));
1200
            $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id));
1201
        }
1202
 
1203
        // All ratings should have been deleted.
1204
        $rm = new \rating_manager();
1205
        foreach ($postsinforum as $post) {
1206
            $ratings = $rm->get_all_ratings_for_item((object) [
1207
                'context' => $context,
1208
                'component' => 'mod_forum',
1209
                'ratingarea' => 'post',
1210
                'itemid' => $post->id,
1211
            ]);
1212
            $this->assertEmpty($ratings);
1213
        }
1214
 
1215
        // All tags should have been deleted.
1216
        $posttags = \core_tag_tag::get_items_tags('mod_forum', 'forum_posts', array_keys($postsinforum));
1217
        foreach ($posttags as $tags) {
1218
            $this->assertEmpty($tags);
1219
        }
1220
 
1221
        // Check the other forum too. It should remain intact.
1222
        $forum = next($forums);
1223
        $context = $contexts[$forum->id];
1224
 
1225
        // Grab the list of discussions and posts in the forum.
1226
        $discussionsinforum = array_filter($discussions, function($discussion) use ($forum) {
1227
            return $discussion->forum == $forum->id;
1228
        });
1229
 
1230
        $postsinforum = array_filter($posts, function($post) use ($discussionsinforum) {
1231
            return isset($discussionsinforum[$post->discussion]);
1232
        });
1233
 
1234
        // Forum discussions and posts should not have been deleted in this forum.
1235
        $this->assertGreaterThan(0, $DB->count_records('forum_discussions', ['forum' => $forum->id]));
1236
 
1237
        list ($insql, $inparams) = $DB->get_in_or_equal(array_keys($discussionsinforum));
1238
        $this->assertGreaterThan(0, $DB->count_records_select('forum_posts', "discussion {$insql}", $inparams));
1239
 
1240
        // Uploaded files relating to this context should remain.
1241
        foreach ($postsinforum as $post) {
1242
            if ($post->parent == 0) {
1243
                $this->assertNotEmpty($fs->get_area_files($context->id, 'mod_forum', 'post', $post->id));
1244
            }
1245
        }
1246
 
1247
        // Ratings should not have been deleted.
1248
        $rm = new \rating_manager();
1249
        foreach ($postsinforum as $post) {
1250
            if (!isset($ratedposts[$post->id])) {
1251
                continue;
1252
            }
1253
            $ratings = $rm->get_all_ratings_for_item((object) [
1254
                'context' => $context,
1255
                'component' => 'mod_forum',
1256
                'ratingarea' => 'post',
1257
                'itemid' => $post->id,
1258
            ]);
1259
            $this->assertNotEmpty($ratings);
1260
        }
1261
 
1262
        // All tags should remain.
1263
        $posttags = \core_tag_tag::get_items_tags('mod_forum', 'forum_posts', array_keys($postsinforum));
1264
        foreach ($posttags as $tags) {
1265
            $this->assertNotEmpty($tags);
1266
        }
1267
    }
1268
 
1269
    /**
1270
     * Ensure that all user data is deleted for a specific context.
1271
     */
11 efrain 1272
    public function test_delete_data_for_user(): void {
1 efrain 1273
        global $DB;
1274
 
1275
        $fs = get_file_storage();
1276
        $course = $this->getDataGenerator()->create_course();
1277
        $users = $this->helper_create_users($course, 5);
1278
 
1279
        $forums = [];
1280
        $contexts = [];
1281
        for ($i = 0; $i < 2; $i++) {
1282
            $forum = $this->getDataGenerator()->create_module('forum', [
1283
                'course' => $course->id,
1284
                'scale' => 100,
1285
            ]);
1286
            $cm = get_coursemodule_from_instance('forum', $forum->id);
1287
            $context = \context_module::instance($cm->id);
1288
            $forums[$forum->id] = $forum;
1289
            $contexts[$forum->id] = $context;
1290
        }
1291
 
1292
        $discussions = [];
1293
        $posts = [];
1294
        $postsbyforum = [];
1295
        foreach ($users as $user) {
1296
            $postsbyforum[$user->id] = [];
1297
            foreach ($forums as $forum) {
1298
                $context = $contexts[$forum->id];
1299
 
1300
                // Create a new discussion + post in the forum.
1301
                list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1302
                $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1303
                $discussions[$discussion->id] = $discussion;
1304
                $postsbyforum[$user->id][$context->id] = [];
1305
 
1306
                // Add a number of replies.
1307
                $posts[$post->id] = $post;
1308
                $thisforumposts[$post->id] = $post;
1309
                $postsbyforum[$user->id][$context->id][$post->id] = $post;
1310
 
1311
                $reply = $this->helper_reply_to_post($post, $user);
1312
                $posts[$reply->id] = $reply;
1313
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1314
 
1315
                $reply = $this->helper_reply_to_post($post, $user);
1316
                $posts[$reply->id] = $reply;
1317
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1318
 
1319
                $reply = $this->helper_reply_to_post($reply, $user);
1320
                $posts[$reply->id] = $reply;
1321
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1322
 
1323
                // Add a fake inline image to the original post.
1324
                $fs->create_file_from_string([
1325
                        'contextid' => $context->id,
1326
                        'component' => 'mod_forum',
1327
                        'filearea'  => 'post',
1328
                        'itemid'    => $post->id,
1329
                        'filepath'  => '/',
1330
                        'filename'  => 'example.jpg',
1331
                    ], 'image contents (not really)');
1332
                // And a fake attachment.
1333
                $fs->create_file_from_string([
1334
                        'contextid' => $context->id,
1335
                        'component' => 'mod_forum',
1336
                        'filearea'  => 'attachment',
1337
                        'itemid'    => $post->id,
1338
                        'filepath'  => '/',
1339
                        'filename'  => 'example.jpg',
1340
                    ], 'image contents (not really)');
1341
            }
1342
        }
1343
 
1344
        // Mark all posts as read by user1.
1345
        $user1 = reset($users);
1346
        foreach ($posts as $post) {
1347
            $discussion = $discussions[$post->discussion];
1348
            $forum = $forums[$discussion->forum];
1349
            $context = $contexts[$forum->id];
1350
 
1351
            // Mark the post as being read by user1.
1352
            forum_tp_add_read_record($user1->id, $post->id);
1353
        }
1354
 
1355
        // Rate and tag all posts.
1356
        $ratedposts = [];
1357
        foreach ($users as $user) {
1358
            foreach ($posts as $post) {
1359
                $discussion = $discussions[$post->discussion];
1360
                $forum = $forums[$discussion->forum];
1361
                $context = $contexts[$forum->id];
1362
 
1363
                // Tag the post.
1364
                \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1365
 
1366
                // Rate the other users content.
1367
                if ($post->userid != $user->id) {
1368
                    $ratedposts[$post->id] = $post;
1369
                    $rm = new \rating_manager();
1370
                    $ratingoptions = (object) [
1371
                        'context' => $context,
1372
                        'component' => 'mod_forum',
1373
                        'ratingarea' => 'post',
1374
                        'itemid' => $post->id,
1375
                        'scaleid' => $forum->scale,
1376
                        'userid' => $user->id,
1377
                    ];
1378
 
1379
                    $rating = new \rating($ratingoptions);
1380
                    $rating->update_rating(75);
1381
                }
1382
            }
1383
        }
1384
 
1385
        // Delete for one of the forums for the first user.
1386
        $firstcontext = reset($contexts);
1387
 
1388
        $deletedpostids = [];
1389
        $otherpostids = [];
1390
        foreach ($postsbyforum as $user => $contexts) {
1391
            foreach ($contexts as $thiscontextid => $theseposts) {
1392
                $thesepostids = array_map(function($post) {
1393
                    return $post->id;
1394
                }, $theseposts);
1395
 
1396
                if ($user == $user1->id && $thiscontextid == $firstcontext->id) {
1397
                    // This post is in the deleted context and by the target user.
1398
                    $deletedpostids = array_merge($deletedpostids, $thesepostids);
1399
                } else {
1400
                    // This post is by another user, or in a non-target context.
1401
                    $otherpostids = array_merge($otherpostids, $thesepostids);
1402
                }
1403
            }
1404
        }
1405
        list($postinsql, $postinparams) = $DB->get_in_or_equal($deletedpostids, SQL_PARAMS_NAMED);
1406
        list($otherpostinsql, $otherpostinparams) = $DB->get_in_or_equal($otherpostids, SQL_PARAMS_NAMED);
1407
 
1408
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
1409
            \core_user::get_user($user1->id),
1410
            'mod_forum',
1411
            [$firstcontext->id]
1412
        );
1413
        provider::delete_data_for_user($approvedcontextlist);
1414
 
1415
        // All posts should remain.
1416
        $this->assertCount(40, $DB->get_records('forum_posts'));
1417
 
1418
        // There should be 8 posts belonging to user1.
1419
        $this->assertCount(8, $DB->get_records('forum_posts', [
1420
                'userid' => $user1->id,
1421
            ]));
1422
 
1423
        // Four of those posts should have been marked as deleted.
1424
        // That means that the deleted flag is set, and both the subject and message are empty.
1425
        $this->assertCount(4, $DB->get_records_select('forum_posts', "userid = :userid AND deleted = :deleted"
1426
                    . " AND " . $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject')
1427
                    . " AND " . $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message')
1428
                , [
1429
                    'userid' => $user1->id,
1430
                    'deleted' => 1,
1431
                    'subject' => '',
1432
                    'message' => '',
1433
                ]));
1434
 
1435
        // Only user1's posts should have been marked this way.
1436
        $this->assertCount(4, $DB->get_records('forum_posts', [
1437
                'deleted' => 1,
1438
            ]));
1439
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1440
            $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject'), [
1441
                'subject' => '',
1442
            ]));
1443
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1444
            $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message'), [
1445
                'message' => '',
1446
            ]));
1447
 
1448
        // Only the posts in the first discussion should have been marked this way.
1449
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1450
            "deleted = :deleted AND id {$postinsql}",
1451
                array_merge($postinparams, [
1452
                    'deleted' => 1,
1453
                ])
1454
            ));
1455
 
1456
        // Ratings should have been removed from the affected posts.
1457
        $this->assertCount(0, $DB->get_records_select('rating', "itemid {$postinsql}", $postinparams));
1458
 
1459
        // Ratings should remain on posts in the other context, and posts not belonging to the affected user.
1460
        $this->assertCount(144, $DB->get_records_select('rating', "itemid {$otherpostinsql}", $otherpostinparams));
1461
 
1462
        // Ratings should remain where the user has rated another person's post.
1463
        $this->assertCount(32, $DB->get_records('rating', ['userid' => $user1->id]));
1464
 
1465
        // Tags for the affected posts should be removed.
1466
        $this->assertCount(0, $DB->get_records_select('tag_instance', "itemid {$postinsql}", $postinparams));
1467
 
1468
        // Tags should remain for the other posts by this user, and all posts by other users.
1469
        $this->assertCount(72, $DB->get_records_select('tag_instance', "itemid {$otherpostinsql}", $otherpostinparams));
1470
 
1471
        // Files for the affected posts should be removed.
1472
        // 5 users * 2 forums * 1 file in each forum
1473
        // Original total: 10
1474
        // One post with file removed.
1475
        $componentsql = "component = 'mod_forum' AND ";
1476
        $this->assertCount(0, $DB->get_records_select('files',
1477
            "{$componentsql} itemid {$postinsql}", $postinparams));
1478
 
1479
        // Files for the other posts should remain.
1480
        $this->assertCount(18, $DB->get_records_select('files',
1481
            "{$componentsql} filename <> '.' AND itemid {$otherpostinsql}", $otherpostinparams));
1482
    }
1483
 
1484
    /**
1485
     * Ensure that user data for specific users is deleted from a specified context.
1486
     */
11 efrain 1487
    public function test_delete_data_for_users(): void {
1 efrain 1488
        global $DB;
1489
 
1490
        $fs = get_file_storage();
1491
        $course = $this->getDataGenerator()->create_course();
1492
        $users = $this->helper_create_users($course, 5);
1493
 
1494
        $forums = [];
1495
        $contexts = [];
1496
        for ($i = 0; $i < 2; $i++) {
1497
            $forum = $this->getDataGenerator()->create_module('forum', [
1498
                'course' => $course->id,
1499
                'scale' => 100,
1500
            ]);
1501
            $cm = get_coursemodule_from_instance('forum', $forum->id);
1502
            $context = \context_module::instance($cm->id);
1503
            $forums[$forum->id] = $forum;
1504
            $contexts[$forum->id] = $context;
1505
        }
1506
 
1507
        $discussions = [];
1508
        $posts = [];
1509
        $postsbyforum = [];
1510
        foreach ($users as $user) {
1511
            $postsbyforum[$user->id] = [];
1512
            foreach ($forums as $forum) {
1513
                $context = $contexts[$forum->id];
1514
 
1515
                // Create a new discussion + post in the forum.
1516
                list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1517
                $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1518
                $discussions[$discussion->id] = $discussion;
1519
                $postsbyforum[$user->id][$context->id] = [];
1520
 
1521
                // Add a number of replies.
1522
                $posts[$post->id] = $post;
1523
                $thisforumposts[$post->id] = $post;
1524
                $postsbyforum[$user->id][$context->id][$post->id] = $post;
1525
 
1526
                $reply = $this->helper_reply_to_post($post, $user);
1527
                $posts[$reply->id] = $reply;
1528
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1529
 
1530
                $reply = $this->helper_reply_to_post($post, $user);
1531
                $posts[$reply->id] = $reply;
1532
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1533
 
1534
                $reply = $this->helper_reply_to_post($reply, $user);
1535
                $posts[$reply->id] = $reply;
1536
                $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1537
 
1538
                // Add a fake inline image to the original post.
1539
                $fs->create_file_from_string([
1540
                        'contextid' => $context->id,
1541
                        'component' => 'mod_forum',
1542
                        'filearea'  => 'post',
1543
                        'itemid'    => $post->id,
1544
                        'filepath'  => '/',
1545
                        'filename'  => 'example.jpg',
1546
                    ], 'image contents (not really)');
1547
                // And a fake attachment.
1548
                $fs->create_file_from_string([
1549
                        'contextid' => $context->id,
1550
                        'component' => 'mod_forum',
1551
                        'filearea'  => 'attachment',
1552
                        'itemid'    => $post->id,
1553
                        'filepath'  => '/',
1554
                        'filename'  => 'example.jpg',
1555
                    ], 'image contents (not really)');
1556
            }
1557
        }
1558
 
1559
        // Mark all posts as read by user1.
1560
        $user1 = reset($users);
1561
        foreach ($posts as $post) {
1562
            $discussion = $discussions[$post->discussion];
1563
            $forum = $forums[$discussion->forum];
1564
            $context = $contexts[$forum->id];
1565
 
1566
            // Mark the post as being read by user1.
1567
            forum_tp_add_read_record($user1->id, $post->id);
1568
        }
1569
 
1570
        // Rate and tag all posts.
1571
        $ratedposts = [];
1572
        foreach ($users as $user) {
1573
            foreach ($posts as $post) {
1574
                $discussion = $discussions[$post->discussion];
1575
                $forum = $forums[$discussion->forum];
1576
                $context = $contexts[$forum->id];
1577
 
1578
                // Tag the post.
1579
                \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1580
 
1581
                // Rate the other users content.
1582
                if ($post->userid != $user->id) {
1583
                    $ratedposts[$post->id] = $post;
1584
                    $rm = new \rating_manager();
1585
                    $ratingoptions = (object) [
1586
                        'context' => $context,
1587
                        'component' => 'mod_forum',
1588
                        'ratingarea' => 'post',
1589
                        'itemid' => $post->id,
1590
                        'scaleid' => $forum->scale,
1591
                        'userid' => $user->id,
1592
                    ];
1593
 
1594
                    $rating = new \rating($ratingoptions);
1595
                    $rating->update_rating(75);
1596
                }
1597
            }
1598
        }
1599
 
1600
        // Delete for one of the forums for the first user.
1601
        $firstcontext = reset($contexts);
1602
 
1603
        $deletedpostids = [];
1604
        $otherpostids = [];
1605
        foreach ($postsbyforum as $user => $contexts) {
1606
            foreach ($contexts as $thiscontextid => $theseposts) {
1607
                $thesepostids = array_map(function($post) {
1608
                    return $post->id;
1609
                }, $theseposts);
1610
 
1611
                if ($user == $user1->id && $thiscontextid == $firstcontext->id) {
1612
                    // This post is in the deleted context and by the target user.
1613
                    $deletedpostids = array_merge($deletedpostids, $thesepostids);
1614
                } else {
1615
                    // This post is by another user, or in a non-target context.
1616
                    $otherpostids = array_merge($otherpostids, $thesepostids);
1617
                }
1618
            }
1619
        }
1620
        list($postinsql, $postinparams) = $DB->get_in_or_equal($deletedpostids, SQL_PARAMS_NAMED);
1621
        list($otherpostinsql, $otherpostinparams) = $DB->get_in_or_equal($otherpostids, SQL_PARAMS_NAMED);
1622
 
1623
        $approveduserlist = new \core_privacy\local\request\approved_userlist($firstcontext, 'mod_forum', [$user1->id]);
1624
        provider::delete_data_for_users($approveduserlist);
1625
 
1626
        // All posts should remain.
1627
        $this->assertCount(40, $DB->get_records('forum_posts'));
1628
 
1629
        // There should be 8 posts belonging to user1.
1630
        $this->assertCount(8, $DB->get_records('forum_posts', [
1631
                'userid' => $user1->id,
1632
            ]));
1633
 
1634
        // Four of those posts should have been marked as deleted.
1635
        // That means that the deleted flag is set, and both the subject and message are empty.
1636
        $this->assertCount(4, $DB->get_records_select('forum_posts', "userid = :userid AND deleted = :deleted"
1637
                    . " AND " . $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject')
1638
                    . " AND " . $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message')
1639
                , [
1640
                    'userid' => $user1->id,
1641
                    'deleted' => 1,
1642
                    'subject' => '',
1643
                    'message' => '',
1644
                ]));
1645
 
1646
        // Only user1's posts should have been marked this way.
1647
        $this->assertCount(4, $DB->get_records('forum_posts', [
1648
                'deleted' => 1,
1649
            ]));
1650
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1651
            $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject'), [
1652
                'subject' => '',
1653
            ]));
1654
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1655
            $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message'), [
1656
                'message' => '',
1657
            ]));
1658
 
1659
        // Only the posts in the first discussion should have been marked this way.
1660
        $this->assertCount(4, $DB->get_records_select('forum_posts',
1661
            "deleted = :deleted AND id {$postinsql}",
1662
                array_merge($postinparams, [
1663
                    'deleted' => 1,
1664
                ])
1665
            ));
1666
 
1667
        // Ratings should have been removed from the affected posts.
1668
        $this->assertCount(0, $DB->get_records_select('rating', "itemid {$postinsql}", $postinparams));
1669
 
1670
        // Ratings should remain on posts in the other context, and posts not belonging to the affected user.
1671
        $this->assertCount(144, $DB->get_records_select('rating', "itemid {$otherpostinsql}", $otherpostinparams));
1672
 
1673
        // Ratings should remain where the user has rated another person's post.
1674
        $this->assertCount(32, $DB->get_records('rating', ['userid' => $user1->id]));
1675
 
1676
        // Tags for the affected posts should be removed.
1677
        $this->assertCount(0, $DB->get_records_select('tag_instance', "itemid {$postinsql}", $postinparams));
1678
 
1679
        // Tags should remain for the other posts by this user, and all posts by other users.
1680
        $this->assertCount(72, $DB->get_records_select('tag_instance', "itemid {$otherpostinsql}", $otherpostinparams));
1681
 
1682
        // Files for the affected posts should be removed.
1683
        // 5 users * 2 forums * 1 file in each forum
1684
        // Original total: 10
1685
        // One post with file removed.
1686
        $componentsql = "component = 'mod_forum' AND ";
1687
        $this->assertCount(0, $DB->get_records_select('files',
1688
            "{$componentsql} itemid {$postinsql}", $postinparams));
1689
 
1690
        // Files for the other posts should remain.
1691
        $this->assertCount(18,
1692
                $DB->get_records_select('files',
1693
                    "{$componentsql} filename <> '.' AND itemid {$otherpostinsql}", $otherpostinparams));
1694
    }
1695
 
1696
    /**
1697
     * Ensure that the discussion author is listed as a user in the context.
1698
     */
11 efrain 1699
    public function test_get_users_in_context_post_author(): void {
1 efrain 1700
        global $DB;
1701
        $component = 'mod_forum';
1702
 
1703
        $course = $this->getDataGenerator()->create_course();
1704
 
1705
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1706
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1707
        $context = \context_module::instance($cm->id);
1708
 
1709
        list($author, $user) = $this->helper_create_users($course, 2);
1710
 
1711
        list($fd1, $fp1) = $this->helper_post_to_forum($forum, $author);
1712
 
1713
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1714
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1715
 
1716
        // There should only be one user in the list.
1717
        $this->assertCount(1, $userlist);
1718
        $this->assertEquals([$author->id], $userlist->get_userids());
1719
    }
1720
 
1721
    /**
1722
     * Ensure that all post authors are included as a user in the context.
1723
     */
11 efrain 1724
    public function test_get_users_in_context_post_authors(): void {
1 efrain 1725
        global $DB;
1726
        $component = 'mod_forum';
1727
 
1728
        $course = $this->getDataGenerator()->create_course();
1729
 
1730
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1731
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1732
        $context = \context_module::instance($cm->id);
1733
 
1734
        list($author, $user, $other) = $this->helper_create_users($course, 3);
1735
 
1736
        list($fd1, $fp1) = $this->helper_post_to_forum($forum, $author);
1737
        $fp1reply = $this->helper_post_to_discussion($forum, $fd1, $user);
1738
        $fd1 = $DB->get_record('forum_discussions', ['id' => $fd1->id]);
1739
 
1740
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1741
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1742
 
1743
        // Two users - author and replier.
1744
        $this->assertCount(2, $userlist);
1745
 
1746
        $expected = [$author->id, $user->id];
1747
        sort($expected);
1748
 
1749
        $actual = $userlist->get_userids();
1750
        sort($actual);
1751
 
1752
        $this->assertEquals($expected, $actual);
1753
    }
1754
 
1755
    /**
1756
     * Ensure that all post raters are included as a user in the context.
1757
     */
11 efrain 1758
    public function test_get_users_in_context_post_ratings(): void {
1 efrain 1759
        global $DB;
1760
        $component = 'mod_forum';
1761
 
1762
        $course = $this->getDataGenerator()->create_course();
1763
 
1764
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1765
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1766
        $context = \context_module::instance($cm->id);
1767
 
1768
        list($author, $user, $other) = $this->helper_create_users($course, 3);
1769
 
1770
        list($fd1, $fp1) = $this->helper_post_to_forum($forum, $author);
1771
 
1772
        // Rate the other users content.
1773
        $rm = new \rating_manager();
1774
        $ratingoptions = (object) [
1775
            'context' => $context,
1776
            'component' => 'mod_forum',
1777
            'ratingarea' => 'post',
1778
            'itemid' => $fp1->id,
1779
            'scaleid' => $forum->scale,
1780
            'userid' => $user->id,
1781
        ];
1782
 
1783
        $rating = new \rating($ratingoptions);
1784
        $rating->update_rating(75);
1785
 
1786
        $fp1reply = $this->helper_post_to_discussion($forum, $fd1, $author);
1787
        $fd1 = $DB->get_record('forum_discussions', ['id' => $fd1->id]);
1788
 
1789
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1790
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1791
 
1792
        // Two users - author and rater.
1793
        $this->assertCount(2, $userlist);
1794
 
1795
        $expected = [$author->id, $user->id];
1796
        sort($expected);
1797
 
1798
        $actual = $userlist->get_userids();
1799
        sort($actual);
1800
 
1801
        $this->assertEquals($expected, $actual);
1802
    }
1803
 
1804
    /**
1805
     * Ensure that all users with a digest preference are included as a user in the context.
1806
     */
11 efrain 1807
    public function test_get_users_in_context_digest_preference(): void {
1 efrain 1808
        global $DB;
1809
        $component = 'mod_forum';
1810
 
1811
        $course = $this->getDataGenerator()->create_course();
1812
 
1813
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1814
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1815
        $context = \context_module::instance($cm->id);
1816
 
1817
        $otherforum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1818
        $othercm = get_coursemodule_from_instance('forum', $otherforum->id);
1819
        $othercontext = \context_module::instance($othercm->id);
1820
 
1821
        list($user, $otheruser) = $this->helper_create_users($course, 2);
1822
 
1823
        // Add digest subscriptions.
1824
        forum_set_user_maildigest($forum, 0, $user);
1825
        forum_set_user_maildigest($otherforum, 0, $otheruser);
1826
 
1827
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1828
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1829
 
1830
        // One user - the one with a digest preference.
1831
        $this->assertCount(1, $userlist);
1832
 
1833
        $expected = [$user->id];
1834
        sort($expected);
1835
 
1836
        $actual = $userlist->get_userids();
1837
        sort($actual);
1838
 
1839
        $this->assertEquals($expected, $actual);
1840
    }
1841
 
1842
    /**
1843
     * Ensure that all users with a forum subscription preference included as a user in the context.
1844
     */
11 efrain 1845
    public function test_get_users_in_context_with_subscription(): void {
1 efrain 1846
        global $DB;
1847
        $component = 'mod_forum';
1848
 
1849
        $course = $this->getDataGenerator()->create_course();
1850
 
1851
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1852
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1853
        $context = \context_module::instance($cm->id);
1854
 
1855
        $otherforum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1856
        $othercm = get_coursemodule_from_instance('forum', $otherforum->id);
1857
        $othercontext = \context_module::instance($othercm->id);
1858
 
1859
        list($user, $otheruser) = $this->helper_create_users($course, 2);
1860
 
1861
        // Subscribe the user to the forum.
1862
        \mod_forum\subscriptions::subscribe_user($user->id, $forum);
1863
 
1864
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1865
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1866
 
1867
        // One user - the one with a digest preference.
1868
        $this->assertCount(1, $userlist);
1869
 
1870
        $expected = [$user->id];
1871
        sort($expected);
1872
 
1873
        $actual = $userlist->get_userids();
1874
        sort($actual);
1875
 
1876
        $this->assertEquals($expected, $actual);
1877
    }
1878
 
1879
    /**
1880
     * Ensure that all users with a per-discussion subscription preference included as a user in the context.
1881
     */
11 efrain 1882
    public function test_get_users_in_context_with_discussion_subscription(): void {
1 efrain 1883
        global $DB;
1884
        $component = 'mod_forum';
1885
 
1886
        $course = $this->getDataGenerator()->create_course();
1887
 
1888
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1889
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1890
        $context = \context_module::instance($cm->id);
1891
 
1892
        $otherforum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1893
        $othercm = get_coursemodule_from_instance('forum', $otherforum->id);
1894
        $othercontext = \context_module::instance($othercm->id);
1895
 
1896
        list($author, $user, $otheruser) = $this->helper_create_users($course, 3);
1897
 
1898
        // Post in both of the forums.
1899
        list($fd1, $fp1) = $this->helper_post_to_forum($forum, $author);
1900
        list($ofd1, $ofp1) = $this->helper_post_to_forum($otherforum, $author);
1901
 
1902
        // Subscribe the user to the discussions.
1903
        \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $fd1);
1904
        \mod_forum\subscriptions::subscribe_user_to_discussion($otheruser->id, $ofd1);
1905
 
1906
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1907
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1908
 
1909
        // Two users - the author, and the one who subscribed.
1910
        $this->assertCount(2, $userlist);
1911
 
1912
        $expected = [$author->id, $user->id];
1913
        sort($expected);
1914
 
1915
        $actual = $userlist->get_userids();
1916
        sort($actual);
1917
 
1918
        $this->assertEquals($expected, $actual);
1919
    }
1920
 
1921
    /**
1922
     * Ensure that all users with read tracking are included as a user in the context.
1923
     */
11 efrain 1924
    public function test_get_users_in_context_with_read_post_tracking(): void {
1 efrain 1925
        global $DB;
1926
        $component = 'mod_forum';
1927
 
1928
        $course = $this->getDataGenerator()->create_course();
1929
 
1930
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1931
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1932
        $context = \context_module::instance($cm->id);
1933
 
1934
        $otherforum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1935
        $othercm = get_coursemodule_from_instance('forum', $otherforum->id);
1936
        $othercontext = \context_module::instance($othercm->id);
1937
 
1938
        list($author, $user, $otheruser) = $this->helper_create_users($course, 3);
1939
 
1940
        // Post in both of the forums.
1941
        list($fd1, $fp1) = $this->helper_post_to_forum($forum, $author);
1942
        list($ofd1, $ofp1) = $this->helper_post_to_forum($otherforum, $author);
1943
 
1944
        // Add read information for those users.
1945
        forum_tp_add_read_record($user->id, $fp1->id);
1946
        forum_tp_add_read_record($otheruser->id, $ofp1->id);
1947
 
1948
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1949
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1950
 
1951
        // Two user - the author, and the one who has read the post.
1952
        $this->assertCount(2, $userlist);
1953
 
1954
        $expected = [$author->id, $user->id];
1955
        sort($expected);
1956
 
1957
        $actual = $userlist->get_userids();
1958
        sort($actual);
1959
 
1960
        $this->assertEquals($expected, $actual);
1961
    }
1962
 
1963
    /**
1964
     * Ensure that all users with tracking preferences are included as a user in the context.
1965
     */
11 efrain 1966
    public function test_get_users_in_context_with_tracking_preferences(): void {
1 efrain 1967
        global $DB;
1968
        $component = 'mod_forum';
1969
 
1970
        $course = $this->getDataGenerator()->create_course();
1971
 
1972
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1973
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1974
        $context = \context_module::instance($cm->id);
1975
 
1976
        $otherforum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1977
        $othercm = get_coursemodule_from_instance('forum', $otherforum->id);
1978
        $othercontext = \context_module::instance($othercm->id);
1979
 
1980
        list($author, $user, $otheruser) = $this->helper_create_users($course, 3);
1981
 
1982
        // Forum tracking is opt-out.
1983
        // Stop tracking the read posts.
1984
        forum_tp_stop_tracking($forum->id, $user->id);
1985
        forum_tp_stop_tracking($otherforum->id, $otheruser->id);
1986
 
1987
        $userlist = new \core_privacy\local\request\userlist($context, $component);
1988
        \mod_forum\privacy\provider::get_users_in_context($userlist);
1989
 
1990
        // One user - the one who is tracking that forum.
1991
        $this->assertCount(1, $userlist);
1992
 
1993
        $expected = [$user->id];
1994
        sort($expected);
1995
 
1996
        $actual = $userlist->get_userids();
1997
        sort($actual);
1998
 
1999
        $this->assertEquals($expected, $actual);
2000
    }
2001
 
2002
    /**
2003
     * Test exporting plugin user preferences
2004
     */
2005
    public function test_export_user_preferences(): void {
2006
        $this->setAdminUser();
2007
 
2008
        // Create a user with some forum preferences.
2009
        $user = $this->getDataGenerator()->create_user([
2010
            'maildigest' => 2,
2011
            'autosubscribe' => 1,
2012
            'trackforums' => 0,
2013
        ]);
2014
 
2015
        set_user_preference('markasreadonnotification', 0, $user);
2016
        set_user_preference('forum_discussionlistsortorder', \mod_forum\local\vaults\discussion_list::SORTORDER_STARTER_ASC,
2017
            $user);
2018
 
2019
        // Export test users preferences.
2020
        provider::export_user_preferences($user->id);
2021
 
2022
        $writer = \core_privacy\local\request\writer::with_context(\context_system::instance());
2023
        $this->assertTrue($writer->has_any_data());
2024
 
2025
        $preferences = (array) $writer->get_user_preferences('mod_forum');
2026
 
2027
        $this->assertEquals((object) [
2028
            'value' => 2,
2029
            'description' => get_string('emaildigestsubjects'),
2030
        ], $preferences['maildigest']);
2031
 
2032
        $this->assertEquals((object) [
2033
            'value' => 1,
2034
            'description' => get_string('autosubscribeyes'),
2035
        ], $preferences['autosubscribe']);
2036
 
2037
        $this->assertEquals((object) [
2038
            'value' => 0,
2039
            'description' => get_string('trackforumsno'),
2040
        ], $preferences['trackforums']);
2041
 
2042
        $this->assertEquals((object) [
2043
            'value' => 0,
2044
            'description' => get_string('markasreadonnotificationno', 'mod_forum'),
2045
        ], $preferences['markasreadonnotification']);
2046
 
2047
        $this->assertEquals((object) [
2048
            'value' => \mod_forum\local\vaults\discussion_list::SORTORDER_STARTER_ASC,
2049
            'description' => get_string('discussionlistsortbystarterasc', 'mod_forum'),
2050
        ], $preferences['forum_discussionlistsortorder']);
2051
    }
2052
}