Proyectos de Subversion Moodle

Rev

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