2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Tests for the forum implementation of the Privacy Provider API.
21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
29 require_once(__DIR__ . '/helper.php');
30 require_once($CFG->dirroot . '/rating/lib.php');
32 use \mod_forum\privacy\provider;
35 * Tests for the forum implementation of the Privacy Provider API.
37 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class mod_forum_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
42 // Include the privacy subcontext_info trait.
43 // This includes the subcontext builders.
44 use \mod_forum\privacy\subcontext_info;
46 // Include the mod_forum test helpers.
47 // This includes functions to create forums, users, discussions, and posts.
50 // Include the privacy helper trait for the ratings API.
51 use \core_rating\phpunit\privacy_helper;
53 // Include the privacy helper trait for the tag API.
54 use \core_tag\tests\privacy_helper;
59 public function setUp() {
60 $this->resetAfterTest(true);
64 * Helper to assert that the forum data is correct.
66 * @param object $expected The expected data in the forum.
67 * @param object $actual The actual data in the forum.
69 protected function assert_forum_data($expected, $actual) {
71 $this->assertEquals(format_string($expected->name, true), $actual->name);
75 * Helper to assert that the discussion data is correct.
77 * @param object $expected The expected data in the discussion.
78 * @param object $actual The actual data in the discussion.
80 protected function assert_discussion_data($expected, $actual) {
82 $this->assertEquals(format_string($expected->name, true), $actual->name);
84 \core_privacy\local\request\transform::yesno($expected->pinned),
89 \core_privacy\local\request\transform::datetime($expected->timemodified),
94 \core_privacy\local\request\transform::datetime($expected->usermodified),
100 * Helper to assert that the post data is correct.
102 * @param object $expected The expected data in the post.
103 * @param object $actual The actual data in the post.
104 * @param \core_privacy\local\request\writer $writer The writer used
106 protected function assert_post_data($expected, $actual, $writer) {
108 $this->assertEquals(format_string($expected->subject, true), $actual->subject);
110 // The message should have been passed through the rewriter.
111 // Note: The testable rewrite_pluginfile_urls function in the ignores all items except the text.
113 $writer->rewrite_pluginfile_urls([], '', '', '', $expected->message),
118 \core_privacy\local\request\transform::datetime($expected->created),
123 \core_privacy\local\request\transform::datetime($expected->modified),
129 * Test that a user who is enrolled in a course, but who has never
130 * posted and has no other metadata stored will not have any link to
133 public function test_user_has_never_posted() {
134 // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
135 $course = $this->getDataGenerator()->create_course();
136 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
137 $course = $this->getDataGenerator()->create_course();
138 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
139 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
140 list($user, $otheruser) = $this->helper_create_users($course, 2);
141 list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
142 $cm = get_coursemodule_from_instance('forum', $forum->id);
143 $context = \context_module::instance($cm->id);
145 // Test that no contexts were retrieved.
146 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
147 $contexts = $contextlist->get_contextids();
148 $this->assertCount(0, $contexts);
150 // Attempting to export data for this context should return nothing either.
151 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
153 $writer = \core_privacy\local\request\writer::with_context($context);
155 // The provider should always export data for any context explicitly asked of it, but there should be no
156 // metadata, files, or discussions.
157 $this->assertEmpty($writer->get_data([get_string('discussions', 'mod_forum')]));
158 $this->assertEmpty($writer->get_all_metadata([]));
159 $this->assertEmpty($writer->get_files([]));
163 * Test that a user who is enrolled in a course, and who has never
164 * posted and has subscribed to the forum will have relevant
165 * information returned.
167 public function test_user_has_never_posted_subscribed_to_forum() {
170 // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
171 $course = $this->getDataGenerator()->create_course();
172 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
173 $course = $this->getDataGenerator()->create_course();
174 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
175 $course = $this->getDataGenerator()->create_course();
176 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
177 list($user, $otheruser) = $this->helper_create_users($course, 2);
178 list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
179 $cm = get_coursemodule_from_instance('forum', $forum->id);
180 $context = \context_module::instance($cm->id);
182 // Subscribe the user to the forum.
183 \mod_forum\subscriptions::subscribe_user($user->id, $forum);
185 // Retrieve all contexts - only this context should be returned.
186 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
187 $this->assertCount(1, $contextlist);
188 $this->assertEquals($context, $contextlist->current());
190 // Export all of the data for the context.
191 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
192 $writer = \core_privacy\local\request\writer::with_context($context);
193 $this->assertTrue($writer->has_any_data());
195 $subcontext = $this->get_subcontext($forum);
196 // There should be one item of metadata.
197 $this->assertCount(1, $writer->get_all_metadata($subcontext));
199 // It should be the subscriptionpreference whose value is 1.
200 $this->assertEquals(1, $writer->get_metadata($subcontext, 'subscriptionpreference'));
202 // There should be data about the forum itself.
203 $this->assertNotEmpty($writer->get_data($subcontext));
205 // Delete the data now.
206 // Only the post by the user under test will be removed.
207 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
208 \core_user::get_user($user->id),
212 $this->assertCount(1, $DB->get_records('forum_subscriptions', ['userid' => $user->id]));
213 provider::delete_data_for_user($approvedcontextlist);
214 $this->assertCount(0, $DB->get_records('forum_subscriptions', ['userid' => $user->id]));
218 * Test that a user who is enrolled in a course, and who has never
219 * posted and has subscribed to the discussion will have relevant
220 * information returned.
222 public function test_user_has_never_posted_subscribed_to_discussion() {
225 // Create a course, with a forum, our user under test, another user, and a discussion + post from the other user.
226 $course = $this->getDataGenerator()->create_course();
227 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
228 $course = $this->getDataGenerator()->create_course();
229 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
230 $course = $this->getDataGenerator()->create_course();
231 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
232 list($user, $otheruser) = $this->helper_create_users($course, 2);
233 // Post twice - only the second discussion should be included.
234 $this->helper_post_to_forum($forum, $otheruser);
235 list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
236 $cm = get_coursemodule_from_instance('forum', $forum->id);
237 $context = \context_module::instance($cm->id);
239 // Subscribe the user to the discussion.
240 \mod_forum\subscriptions::subscribe_user_to_discussion($user->id, $discussion);
242 // Retrieve all contexts - only this context should be returned.
243 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
244 $this->assertCount(1, $contextlist);
245 $this->assertEquals($context, $contextlist->current());
247 // Export all of the data for the context.
248 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
249 $writer = \core_privacy\local\request\writer::with_context($context);
250 $this->assertTrue($writer->has_any_data());
252 // There should be nothing in the forum. The user is not subscribed there.
253 $forumsubcontext = $this->get_subcontext($forum);
254 $this->assertCount(0, $writer->get_all_metadata($forumsubcontext));
255 $this->assert_forum_data($forum, $writer->get_data($forumsubcontext));
257 // There should be metadata in the discussion.
258 $discsubcontext = $this->get_subcontext($forum, $discussion);
259 $this->assertCount(1, $writer->get_all_metadata($discsubcontext));
261 // It should be the subscriptionpreference whose value is an Integer.
262 // (It's a timestamp, but it doesn't matter).
263 $metadata = $writer->get_metadata($discsubcontext, 'subscriptionpreference');
264 $this->assertGreaterThan(1, $metadata);
266 // For context we output the discussion content.
267 $data = $writer->get_data($discsubcontext);
268 $this->assertInstanceOf('stdClass', $data);
269 $this->assert_discussion_data($discussion, $data);
271 // Post content is not exported unless the user participated.
272 $postsubcontext = $this->get_subcontext($forum, $discussion, $post);
273 $this->assertCount(0, $writer->get_data($postsubcontext));
275 // Delete the data now.
276 // Only the post by the user under test will be removed.
277 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
278 \core_user::get_user($user->id),
282 $this->assertCount(1, $DB->get_records('forum_discussion_subs', ['userid' => $user->id]));
283 provider::delete_data_for_user($approvedcontextlist);
284 $this->assertCount(0, $DB->get_records('forum_discussion_subs', ['userid' => $user->id]));
288 * Test that a user who has posted their own discussion will have all
291 public function test_user_has_posted_own_discussion() {
292 $course = $this->getDataGenerator()->create_course();
293 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
294 $course = $this->getDataGenerator()->create_course();
295 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
296 $course = $this->getDataGenerator()->create_course();
297 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
298 list($user, $otheruser) = $this->helper_create_users($course, 2);
300 // Post twice - only the second discussion should be included.
301 list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
302 list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $otheruser);
303 $cm = get_coursemodule_from_instance('forum', $forum->id);
304 $context = \context_module::instance($cm->id);
306 // Retrieve all contexts - only this context should be returned.
307 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
308 $this->assertCount(1, $contextlist);
309 $this->assertEquals($context, $contextlist->current());
311 // Export all of the data for the context.
312 $this->setUser($user);
313 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
314 $writer = \core_privacy\local\request\writer::with_context($context);
315 $this->assertTrue($writer->has_any_data());
317 // The other discussion should not have been returned as we did not post in it.
318 $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $otherdiscussion)));
320 $this->assert_discussion_data($discussion, $writer->get_data($this->get_subcontext($forum, $discussion)));
321 $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
325 * Test that a user who has posted a reply to another users discussion will have all content returned, and
326 * appropriate content removed.
328 public function test_user_has_posted_reply() {
331 // Create several courses and forums. We only insert data into the final one.
332 $course = $this->getDataGenerator()->create_course();
333 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
334 $course = $this->getDataGenerator()->create_course();
335 $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
337 $course = $this->getDataGenerator()->create_course();
338 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
339 list($user, $otheruser) = $this->helper_create_users($course, 2);
340 // Post twice - only the second discussion should be included.
341 list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
342 list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $otheruser);
343 $cm = get_coursemodule_from_instance('forum', $forum->id);
344 $context = \context_module::instance($cm->id);
346 // Post a reply to the other person's post.
347 $reply = $this->helper_reply_to_post($post, $user);
349 // Testing as user $user.
350 $this->setUser($user);
352 // Retrieve all contexts - only this context should be returned.
353 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
354 $this->assertCount(1, $contextlist);
355 $this->assertEquals($context, $contextlist->current());
357 // Export all of the data for the context.
358 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
359 $writer = \core_privacy\local\request\writer::with_context($context);
360 $this->assertTrue($writer->has_any_data());
362 // Refresh the discussions.
363 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
364 $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
366 // The other discussion should not have been returned as we did not post in it.
367 $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $otherdiscussion)));
369 // Our discussion should have been returned as we did post in it.
370 $data = $writer->get_data($this->get_subcontext($forum, $discussion));
371 $this->assertNotEmpty($data);
372 $this->assert_discussion_data($discussion, $data);
374 // The reply will be included.
375 $this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
377 // Delete the data now.
378 // Only the post by the user under test will be removed.
379 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
380 \core_user::get_user($user->id),
384 provider::delete_data_for_user($approvedcontextlist);
386 $reply = $DB->get_record('forum_posts', ['id' => $reply->id]);
387 $this->assertEmpty($reply->subject);
388 $this->assertEmpty($reply->message);
389 $this->assertEquals(1, $reply->deleted);
391 $post = $DB->get_record('forum_posts', ['id' => $post->id]);
392 $this->assertNotEmpty($post->subject);
393 $this->assertNotEmpty($post->message);
394 $this->assertEquals(0, $post->deleted);
398 * Test that the rating of another users content will have only the
399 * rater's information returned.
401 public function test_user_has_rated_others() {
404 $course = $this->getDataGenerator()->create_course();
405 $forum = $this->getDataGenerator()->create_module('forum', [
406 'course' => $course->id,
409 list($user, $otheruser) = $this->helper_create_users($course, 2);
410 list($discussion, $post) = $this->helper_post_to_forum($forum, $otheruser);
411 $cm = get_coursemodule_from_instance('forum', $forum->id);
412 $context = \context_module::instance($cm->id);
414 // Rate the other users content.
415 $rm = new rating_manager();
416 $ratingoptions = new stdClass;
417 $ratingoptions->context = $context;
418 $ratingoptions->component = 'mod_forum';
419 $ratingoptions->ratingarea = 'post';
420 $ratingoptions->itemid = $post->id;
421 $ratingoptions->scaleid = $forum->scale;
422 $ratingoptions->userid = $user->id;
424 $rating = new \rating($ratingoptions);
425 $rating->update_rating(75);
427 // Run as the user under test.
428 $this->setUser($user);
430 // Retrieve all contexts - only this context should be returned.
431 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
432 $this->assertCount(1, $contextlist);
433 $this->assertEquals($context, $contextlist->current());
435 // Export all of the data for the context.
436 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
437 $writer = \core_privacy\local\request\writer::with_context($context);
438 $this->assertTrue($writer->has_any_data());
440 // The discussion should not have been returned as we did not post in it.
441 $this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
443 $this->assert_all_own_ratings_on_context(
446 $this->get_subcontext($forum, $discussion, $post),
452 // The original post will not be included.
453 $this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
455 // Delete the data of the user who rated the other user.
456 // The rating should not be deleted as it the rating is considered grading data.
457 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
458 \core_user::get_user($user->id),
462 provider::delete_data_for_user($approvedcontextlist);
464 // Ratings should remain as they are of another user's content.
465 $this->assertCount(1, $DB->get_records('rating', ['itemid' => $post->id]));
469 * Test that ratings of a users own content will all be returned.
471 public function test_user_has_been_rated() {
474 $course = $this->getDataGenerator()->create_course();
475 $forum = $this->getDataGenerator()->create_module('forum', [
476 'course' => $course->id,
479 list($user, $otheruser, $anotheruser) = $this->helper_create_users($course, 3);
480 list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
481 $cm = get_coursemodule_from_instance('forum', $forum->id);
482 $context = \context_module::instance($cm->id);
484 // Other users rate my content.
485 $rm = new rating_manager();
486 $ratingoptions = new stdClass;
487 $ratingoptions->context = $context;
488 $ratingoptions->component = 'mod_forum';
489 $ratingoptions->ratingarea = 'post';
490 $ratingoptions->itemid = $post->id;
491 $ratingoptions->scaleid = $forum->scale;
493 $ratingoptions->userid = $otheruser->id;
494 $rating = new \rating($ratingoptions);
495 $rating->update_rating(75);
497 $ratingoptions->userid = $anotheruser->id;
498 $rating = new \rating($ratingoptions);
499 $rating->update_rating(75);
501 // Run as the user under test.
502 $this->setUser($user);
504 // Retrieve all contexts - only this context should be returned.
505 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
506 $this->assertCount(1, $contextlist);
507 $this->assertEquals($context, $contextlist->current());
509 // Export all of the data for the context.
510 $this->export_context_data_for_user($user->id, $context, 'mod_forum');
511 $writer = \core_privacy\local\request\writer::with_context($context);
512 $this->assertTrue($writer->has_any_data());
514 $this->assert_all_ratings_on_context(
516 $this->get_subcontext($forum, $discussion, $post),
522 // Delete the data of the user who was rated.
523 // The rating should now be deleted.
524 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
525 \core_user::get_user($user->id),
529 provider::delete_data_for_user($approvedcontextlist);
531 // Ratings should remain as they are of another user's content.
532 $this->assertCount(0, $DB->get_records('rating', ['itemid' => $post->id]));
536 * Test that per-user daily digest settings are included correctly.
538 public function test_user_forum_digest() {
541 $course = $this->getDataGenerator()->create_course();
543 $forum0 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
544 $cm0 = get_coursemodule_from_instance('forum', $forum0->id);
545 $context0 = \context_module::instance($cm0->id);
547 $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
548 $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
549 $context1 = \context_module::instance($cm1->id);
551 $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
552 $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
553 $context2 = \context_module::instance($cm2->id);
555 $forum3 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
556 $cm3 = get_coursemodule_from_instance('forum', $forum3->id);
557 $context3 = \context_module::instance($cm3->id);
559 list($user) = $this->helper_create_users($course, 1);
561 // Set a digest value for each forum.
562 forum_set_user_maildigest($forum0, 0, $user);
563 forum_set_user_maildigest($forum1, 1, $user);
564 forum_set_user_maildigest($forum2, 2, $user);
566 // Run as the user under test.
567 $this->setUser($user);
569 // Retrieve all contexts - three contexts should be returned - the fourth should not be included.
570 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
571 $this->assertCount(3, $contextlist);
579 $contextlistids = $contextlist->get_contextids();
580 sort($contextlistids);
581 $this->assertEquals($contextids, $contextlistids);
583 // Check export data for each context.
584 $this->export_context_data_for_user($user->id, $context0, 'mod_forum');
585 $this->assertEquals(0, \core_privacy\local\request\writer::with_context($context0)->get_metadata([], 'digestpreference'));
587 $this->export_context_data_for_user($user->id, $context1, 'mod_forum');
588 $this->assertEquals(1, \core_privacy\local\request\writer::with_context($context1)->get_metadata([], 'digestpreference'));
590 $this->export_context_data_for_user($user->id, $context2, 'mod_forum');
591 $this->assertEquals(2, \core_privacy\local\request\writer::with_context($context2)->get_metadata([], 'digestpreference'));
593 // Delete the data for one of the users in one of the forums.
594 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
595 \core_user::get_user($user->id),
600 $this->assertEquals(0, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum0->id]));
601 $this->assertEquals(1, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum1->id]));
602 $this->assertEquals(2, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum2->id]));
603 provider::delete_data_for_user($approvedcontextlist);
604 $this->assertEquals(0, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum0->id]));
605 $this->assertFalse($DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum1->id]));
606 $this->assertEquals(2, $DB->get_field('forum_digests', 'maildigest', ['userid' => $user->id, 'forum' => $forum2->id]));
611 * Test that the per-user, per-forum user tracking data is exported.
613 public function test_user_tracking_data() {
616 $course = $this->getDataGenerator()->create_course();
618 $forumoff = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
619 $cmoff = get_coursemodule_from_instance('forum', $forumoff->id);
620 $contextoff = \context_module::instance($cmoff->id);
622 $forumon = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
623 $cmon = get_coursemodule_from_instance('forum', $forumon->id);
624 $contexton = \context_module::instance($cmon->id);
626 list($user) = $this->helper_create_users($course, 1);
628 // Set user tracking data.
629 forum_tp_stop_tracking($forumoff->id, $user->id);
630 forum_tp_start_tracking($forumon->id, $user->id);
632 // Run as the user under test.
633 $this->setUser($user);
635 // Retrieve all contexts - only the forum tracking reads should be included.
636 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
637 $this->assertCount(1, $contextlist);
638 $this->assertEquals($contextoff, $contextlist->current());
640 // Check export data for each context.
641 $this->export_context_data_for_user($user->id, $contextoff, 'mod_forum');
642 $this->assertEquals(0,
643 \core_privacy\local\request\writer::with_context($contextoff)->get_metadata([], 'trackreadpreference'));
645 // Delete the data for one of the users in the 'on' forum.
646 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
647 \core_user::get_user($user->id),
652 $this->assertTrue($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
653 $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
655 provider::delete_data_for_user($approvedcontextlist);
657 $this->assertTrue($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
658 $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
660 // Delete the data for one of the users in the 'off' forum.
661 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
662 \core_user::get_user($user->id),
667 provider::delete_data_for_user($approvedcontextlist);
669 $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumoff->id]));
670 $this->assertFalse($DB->record_exists('forum_track_prefs', ['userid' => $user->id, 'forumid' => $forumon->id]));
674 * Test that the posts which a user has read are returned correctly.
676 public function test_user_read_posts() {
679 $course = $this->getDataGenerator()->create_course();
681 $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
682 $cm1 = get_coursemodule_from_instance('forum', $forum1->id);
683 $context1 = \context_module::instance($cm1->id);
685 $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
686 $cm2 = get_coursemodule_from_instance('forum', $forum2->id);
687 $context2 = \context_module::instance($cm2->id);
689 $forum3 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
690 $cm3 = get_coursemodule_from_instance('forum', $forum3->id);
691 $context3 = \context_module::instance($cm3->id);
693 $forum4 = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
694 $cm4 = get_coursemodule_from_instance('forum', $forum4->id);
695 $context4 = \context_module::instance($cm4->id);
697 list($author, $user) = $this->helper_create_users($course, 2);
699 list($f1d1, $f1p1) = $this->helper_post_to_forum($forum1, $author);
700 $f1p1reply = $this->helper_post_to_discussion($forum1, $f1d1, $author);
701 $f1d1 = $DB->get_record('forum_discussions', ['id' => $f1d1->id]);
702 list($f1d2, $f1p2) = $this->helper_post_to_forum($forum1, $author);
704 list($f2d1, $f2p1) = $this->helper_post_to_forum($forum2, $author);
705 $f2p1reply = $this->helper_post_to_discussion($forum2, $f2d1, $author);
706 $f2d1 = $DB->get_record('forum_discussions', ['id' => $f2d1->id]);
707 list($f2d2, $f2p2) = $this->helper_post_to_forum($forum2, $author);
709 list($f3d1, $f3p1) = $this->helper_post_to_forum($forum3, $author);
710 $f3p1reply = $this->helper_post_to_discussion($forum3, $f3d1, $author);
711 $f3d1 = $DB->get_record('forum_discussions', ['id' => $f3d1->id]);
712 list($f3d2, $f3p2) = $this->helper_post_to_forum($forum3, $author);
714 list($f4d1, $f4p1) = $this->helper_post_to_forum($forum4, $author);
715 $f4p1reply = $this->helper_post_to_discussion($forum4, $f4d1, $author);
716 $f4d1 = $DB->get_record('forum_discussions', ['id' => $f4d1->id]);
717 list($f4d2, $f4p2) = $this->helper_post_to_forum($forum4, $author);
720 // User has read post1, but not the reply or second post in forum1.
721 forum_tp_add_read_record($user->id, $f1p1->id);
723 // User has read post1 and its reply, but not the second post in forum2.
724 forum_tp_add_read_record($user->id, $f2p1->id);
725 forum_tp_add_read_record($user->id, $f2p1reply->id);
727 // User has read post2 in forum3.
728 forum_tp_add_read_record($user->id, $f3p2->id);
730 // Nothing has been read in forum4.
732 // Run as the user under test.
733 $this->setUser($user);
735 // Retrieve all contexts - should be three - forum4 has no data.
736 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
737 $this->assertCount(3, $contextlist);
745 $contextlistids = $contextlist->get_contextids();
746 sort($contextlistids);
747 $this->assertEquals($contextids, $contextlistids);
750 $this->export_context_data_for_user($user->id, $context1, 'mod_forum');
751 $writer = \core_privacy\local\request\writer::with_context($context1);
753 // User has read f1p1.
754 $readdata = $writer->get_metadata(
755 $this->get_subcontext($forum1, $f1d1, $f1p1),
758 $this->assertNotEmpty($readdata);
759 $this->assertTrue(isset($readdata->firstread));
760 $this->assertTrue(isset($readdata->lastread));
762 // User has not f1p1reply.
763 $readdata = $writer->get_metadata(
764 $this->get_subcontext($forum1, $f1d1, $f1p1reply),
767 $this->assertEmpty($readdata);
769 // User has not f1p2.
770 $readdata = $writer->get_metadata(
771 $this->get_subcontext($forum1, $f1d2, $f1p2),
774 $this->assertEmpty($readdata);
777 $this->export_context_data_for_user($user->id, $context2, 'mod_forum');
778 $writer = \core_privacy\local\request\writer::with_context($context2);
780 // User has read f2p1.
781 $readdata = $writer->get_metadata(
782 $this->get_subcontext($forum2, $f2d1, $f2p1),
785 $this->assertNotEmpty($readdata);
786 $this->assertTrue(isset($readdata->firstread));
787 $this->assertTrue(isset($readdata->lastread));
789 // User has read f2p1reply.
790 $readdata = $writer->get_metadata(
791 $this->get_subcontext($forum2, $f2d1, $f2p1reply),
794 $this->assertNotEmpty($readdata);
795 $this->assertTrue(isset($readdata->firstread));
796 $this->assertTrue(isset($readdata->lastread));
798 // User has not read f2p2.
799 $readdata = $writer->get_metadata(
800 $this->get_subcontext($forum2, $f2d2, $f2p2),
803 $this->assertEmpty($readdata);
806 $this->export_context_data_for_user($user->id, $context3, 'mod_forum');
807 $writer = \core_privacy\local\request\writer::with_context($context3);
809 // User has not read f3p1.
810 $readdata = $writer->get_metadata(
811 $this->get_subcontext($forum3, $f3d1, $f3p1),
814 $this->assertEmpty($readdata);
816 // User has not read f3p1reply.
817 $readdata = $writer->get_metadata(
818 $this->get_subcontext($forum3, $f3d1, $f3p1reply),
821 $this->assertEmpty($readdata);
823 // User has read f3p2.
824 $readdata = $writer->get_metadata(
825 $this->get_subcontext($forum3, $f3d2, $f3p2),
828 $this->assertNotEmpty($readdata);
829 $this->assertTrue(isset($readdata->firstread));
830 $this->assertTrue(isset($readdata->lastread));
832 // Delete all data for one of the users in one of the forums.
833 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
834 \core_user::get_user($user->id),
839 $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum1->id]));
840 $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum2->id]));
841 $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum3->id]));
843 provider::delete_data_for_user($approvedcontextlist);
845 $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum1->id]));
846 $this->assertTrue($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum2->id]));
847 $this->assertFalse($DB->record_exists('forum_read', ['userid' => $user->id, 'forumid' => $forum3->id]));
851 * Test that posts with attachments have their attachments correctly exported.
853 public function test_post_attachment_inclusion() {
856 $fs = get_file_storage();
857 $course = $this->getDataGenerator()->create_course();
858 list($author, $otheruser) = $this->helper_create_users($course, 2);
860 $forum = $this->getDataGenerator()->create_module('forum', [
861 'course' => $course->id,
864 $cm = get_coursemodule_from_instance('forum', $forum->id);
865 $context = \context_module::instance($cm->id);
867 // Create a new discussion + post in the forum.
868 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
869 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
871 // Add a number of replies.
872 $reply = $this->helper_reply_to_post($post, $author);
873 $reply = $this->helper_reply_to_post($post, $author);
874 $reply = $this->helper_reply_to_post($reply, $author);
875 $posts[$reply->id] = $reply;
877 // Add a fake inline image to the original post.
878 $createdfile = $fs->create_file_from_string([
879 'contextid' => $context->id,
880 'component' => 'mod_forum',
881 'filearea' => 'post',
882 'itemid' => $post->id,
884 'filename' => 'example.jpg',
886 'image contents (not really)');
888 // Tag the post and the final reply.
889 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
890 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $reply->id, $context, ['example', 'differenttag']);
892 // Create a second discussion + post in the forum without tags.
893 list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $author);
894 $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
896 // Add a number of replies.
897 $reply = $this->helper_reply_to_post($otherpost, $author);
898 $reply = $this->helper_reply_to_post($otherpost, $author);
900 // Run as the user under test.
901 $this->setUser($author);
903 // Retrieve all contexts - should be one.
904 $contextlist = $this->get_contexts_for_userid($author->id, 'mod_forum');
905 $this->assertCount(1, $contextlist);
907 $this->export_context_data_for_user($author->id, $context, 'mod_forum');
908 $writer = \core_privacy\local\request\writer::with_context($context);
910 // The inline file should be on the first forum post.
911 $subcontext = $this->get_subcontext($forum, $discussion, $post);
912 $foundfiles = $writer->get_files($subcontext);
913 $this->assertCount(1, $foundfiles);
914 $this->assertEquals($createdfile, reset($foundfiles));
918 * Test that posts which include tags have those tags exported.
920 public function test_post_tags() {
923 $course = $this->getDataGenerator()->create_course();
924 list($author, $otheruser) = $this->helper_create_users($course, 2);
926 $forum = $this->getDataGenerator()->create_module('forum', [
927 'course' => $course->id,
930 $cm = get_coursemodule_from_instance('forum', $forum->id);
931 $context = \context_module::instance($cm->id);
933 // Create a new discussion + post in the forum.
934 list($discussion, $post) = $this->helper_post_to_forum($forum, $author);
935 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
937 // Add a number of replies.
938 $reply = $this->helper_reply_to_post($post, $author);
939 $reply = $this->helper_reply_to_post($post, $author);
940 $reply = $this->helper_reply_to_post($reply, $author);
941 $posts[$reply->id] = $reply;
943 // Tag the post and the final reply.
944 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
945 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $reply->id, $context, ['example', 'differenttag']);
947 // Create a second discussion + post in the forum without tags.
948 list($otherdiscussion, $otherpost) = $this->helper_post_to_forum($forum, $author);
949 $otherdiscussion = $DB->get_record('forum_discussions', ['id' => $otherdiscussion->id]);
951 // Add a number of replies.
952 $reply = $this->helper_reply_to_post($otherpost, $author);
953 $reply = $this->helper_reply_to_post($otherpost, $author);
955 // Run as the user under test.
956 $this->setUser($author);
958 // Retrieve all contexts - should be two.
959 $contextlist = $this->get_contexts_for_userid($author->id, 'mod_forum');
960 $this->assertCount(1, $contextlist);
962 $this->export_all_data_for_user($author->id, 'mod_forum');
963 $writer = \core_privacy\local\request\writer::with_context($context);
965 $this->assert_all_tags_match_on_context(
968 $this->get_subcontext($forum, $discussion, $post),
976 * Ensure that all user data is deleted from a context.
978 public function test_all_users_deleted_from_context() {
981 $fs = get_file_storage();
982 $course = $this->getDataGenerator()->create_course();
983 $users = $this->helper_create_users($course, 5);
987 for ($i = 0; $i < 2; $i++) {
988 $forum = $this->getDataGenerator()->create_module('forum', [
989 'course' => $course->id,
992 $cm = get_coursemodule_from_instance('forum', $forum->id);
993 $context = \context_module::instance($cm->id);
994 $forums[$forum->id] = $forum;
995 $contexts[$forum->id] = $context;
1000 foreach ($users as $user) {
1001 foreach ($forums as $forum) {
1002 $context = $contexts[$forum->id];
1004 // Create a new discussion + post in the forum.
1005 list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1006 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1007 $discussions[$discussion->id] = $discussion;
1009 // Add a number of replies.
1010 $posts[$post->id] = $post;
1011 $reply = $this->helper_reply_to_post($post, $user);
1012 $posts[$reply->id] = $reply;
1013 $reply = $this->helper_reply_to_post($post, $user);
1014 $posts[$reply->id] = $reply;
1015 $reply = $this->helper_reply_to_post($reply, $user);
1016 $posts[$reply->id] = $reply;
1018 // Add a fake inline image to the original post.
1019 $fs->create_file_from_string([
1020 'contextid' => $context->id,
1021 'component' => 'mod_forum',
1022 'filearea' => 'post',
1023 'itemid' => $post->id,
1025 'filename' => 'example.jpg',
1026 ], 'image contents (not really)');
1027 // And an attachment.
1028 $fs->create_file_from_string([
1029 'contextid' => $context->id,
1030 'component' => 'mod_forum',
1031 'filearea' => 'attachment',
1032 'itemid' => $post->id,
1034 'filename' => 'example.jpg',
1035 ], 'image contents (not really)');
1039 // Mark all posts as read by user.
1040 $user = reset($users);
1042 foreach ($posts as $post) {
1043 $discussion = $discussions[$post->discussion];
1044 $forum = $forums[$discussion->forum];
1045 $context = $contexts[$forum->id];
1047 // Mark the post as being read by user.
1048 forum_tp_add_read_record($user->id, $post->id);
1051 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1053 // Rate the other users content.
1054 if ($post->userid != $user->id) {
1055 $ratedposts[$post->id] = $post;
1056 $rm = new rating_manager();
1057 $ratingoptions = (object) [
1058 'context' => $context,
1059 'component' => 'mod_forum',
1060 'ratingarea' => 'post',
1061 'itemid' => $post->id,
1062 'scaleid' => $forum->scale,
1063 'userid' => $user->id,
1066 $rating = new \rating($ratingoptions);
1067 $rating->update_rating(75);
1071 // Run as the user under test.
1072 $this->setUser($user);
1074 // Retrieve all contexts - should be two.
1075 $contextlist = $this->get_contexts_for_userid($user->id, 'mod_forum');
1076 $this->assertCount(2, $contextlist);
1078 // These are the contexts we expect.
1079 $contextids = array_map(function($context) {
1080 return $context->id;
1084 $contextlistids = $contextlist->get_contextids();
1085 sort($contextlistids);
1086 $this->assertEquals($contextids, $contextlistids);
1088 // Delete for the first forum.
1089 $forum = reset($forums);
1090 $context = $contexts[$forum->id];
1091 provider::delete_data_for_all_users_in_context($context);
1093 // Determine what should have been deleted.
1094 $discussionsinforum = array_filter($discussions, function($discussion) use ($forum) {
1095 return $discussion->forum == $forum->id;
1098 $postsinforum = array_filter($posts, function($post) use ($discussionsinforum) {
1099 return isset($discussionsinforum[$post->discussion]);
1102 // All forum discussions and posts should have been deleted in this forum.
1103 $this->assertCount(0, $DB->get_records('forum_discussions', ['forum' => $forum->id]));
1105 list ($insql, $inparams) = $DB->get_in_or_equal(array_keys($discussionsinforum));
1106 $this->assertCount(0, $DB->get_records_select('forum_posts', "discussion {$insql}", $inparams));
1108 // All uploaded files relating to this context should have been deleted (post content).
1109 foreach ($postsinforum as $post) {
1110 $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'post', $post->id));
1111 $this->assertEmpty($fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id));
1114 // All ratings should have been deleted.
1115 $rm = new rating_manager();
1116 foreach ($postsinforum as $post) {
1117 $ratings = $rm->get_all_ratings_for_item((object) [
1118 'context' => $context,
1119 'component' => 'mod_forum',
1120 'ratingarea' => 'post',
1121 'itemid' => $post->id,
1123 $this->assertEmpty($ratings);
1126 // All tags should have been deleted.
1127 $posttags = \core_tag_tag::get_items_tags('mod_forum', 'forum_posts', array_keys($postsinforum));
1128 foreach ($posttags as $tags) {
1129 $this->assertEmpty($tags);
1132 // Check the other forum too. It should remain intact.
1133 $forum = next($forums);
1134 $context = $contexts[$forum->id];
1136 // Grab the list of discussions and posts in the forum.
1137 $discussionsinforum = array_filter($discussions, function($discussion) use ($forum) {
1138 return $discussion->forum == $forum->id;
1141 $postsinforum = array_filter($posts, function($post) use ($discussionsinforum) {
1142 return isset($discussionsinforum[$post->discussion]);
1145 // Forum discussions and posts should not have been deleted in this forum.
1146 $this->assertGreaterThan(0, $DB->count_records('forum_discussions', ['forum' => $forum->id]));
1148 list ($insql, $inparams) = $DB->get_in_or_equal(array_keys($discussionsinforum));
1149 $this->assertGreaterThan(0, $DB->count_records_select('forum_posts', "discussion {$insql}", $inparams));
1151 // Uploaded files relating to this context should remain.
1152 foreach ($postsinforum as $post) {
1153 if ($post->parent == 0) {
1154 $this->assertNotEmpty($fs->get_area_files($context->id, 'mod_forum', 'post', $post->id));
1158 // Ratings should not have been deleted.
1159 $rm = new rating_manager();
1160 foreach ($postsinforum as $post) {
1161 if (!isset($ratedposts[$post->id])) {
1164 $ratings = $rm->get_all_ratings_for_item((object) [
1165 'context' => $context,
1166 'component' => 'mod_forum',
1167 'ratingarea' => 'post',
1168 'itemid' => $post->id,
1170 $this->assertNotEmpty($ratings);
1173 // All tags should remain.
1174 $posttags = \core_tag_tag::get_items_tags('mod_forum', 'forum_posts', array_keys($postsinforum));
1175 foreach ($posttags as $tags) {
1176 $this->assertNotEmpty($tags);
1181 * Ensure that all user data is deleted for a specific context.
1183 public function test_delete_data_for_user() {
1186 $fs = get_file_storage();
1187 $course = $this->getDataGenerator()->create_course();
1188 $users = $this->helper_create_users($course, 5);
1192 for ($i = 0; $i < 2; $i++) {
1193 $forum = $this->getDataGenerator()->create_module('forum', [
1194 'course' => $course->id,
1197 $cm = get_coursemodule_from_instance('forum', $forum->id);
1198 $context = \context_module::instance($cm->id);
1199 $forums[$forum->id] = $forum;
1200 $contexts[$forum->id] = $context;
1206 foreach ($users as $user) {
1207 $postsbyforum[$user->id] = [];
1208 foreach ($forums as $forum) {
1209 $context = $contexts[$forum->id];
1211 // Create a new discussion + post in the forum.
1212 list($discussion, $post) = $this->helper_post_to_forum($forum, $user);
1213 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]);
1214 $discussions[$discussion->id] = $discussion;
1215 $postsbyforum[$user->id][$context->id] = [];
1217 // Add a number of replies.
1218 $posts[$post->id] = $post;
1219 $thisforumposts[$post->id] = $post;
1220 $postsbyforum[$user->id][$context->id][$post->id] = $post;
1222 $reply = $this->helper_reply_to_post($post, $user);
1223 $posts[$reply->id] = $reply;
1224 $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1226 $reply = $this->helper_reply_to_post($post, $user);
1227 $posts[$reply->id] = $reply;
1228 $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1230 $reply = $this->helper_reply_to_post($reply, $user);
1231 $posts[$reply->id] = $reply;
1232 $postsbyforum[$user->id][$context->id][$reply->id] = $reply;
1234 // Add a fake inline image to the original post.
1235 $fs->create_file_from_string([
1236 'contextid' => $context->id,
1237 'component' => 'mod_forum',
1238 'filearea' => 'post',
1239 'itemid' => $post->id,
1241 'filename' => 'example.jpg',
1242 ], 'image contents (not really)');
1243 // And a fake attachment.
1244 $fs->create_file_from_string([
1245 'contextid' => $context->id,
1246 'component' => 'mod_forum',
1247 'filearea' => 'attachment',
1248 'itemid' => $post->id,
1250 'filename' => 'example.jpg',
1251 ], 'image contents (not really)');
1255 // Mark all posts as read by user1.
1256 $user1 = reset($users);
1257 foreach ($posts as $post) {
1258 $discussion = $discussions[$post->discussion];
1259 $forum = $forums[$discussion->forum];
1260 $context = $contexts[$forum->id];
1262 // Mark the post as being read by user1.
1263 forum_tp_add_read_record($user1->id, $post->id);
1266 // Rate and tag all posts.
1268 foreach ($users as $user) {
1269 foreach ($posts as $post) {
1270 $discussion = $discussions[$post->discussion];
1271 $forum = $forums[$discussion->forum];
1272 $context = $contexts[$forum->id];
1275 \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['example', 'tag']);
1277 // Rate the other users content.
1278 if ($post->userid != $user->id) {
1279 $ratedposts[$post->id] = $post;
1280 $rm = new rating_manager();
1281 $ratingoptions = (object) [
1282 'context' => $context,
1283 'component' => 'mod_forum',
1284 'ratingarea' => 'post',
1285 'itemid' => $post->id,
1286 'scaleid' => $forum->scale,
1287 'userid' => $user->id,
1290 $rating = new \rating($ratingoptions);
1291 $rating->update_rating(75);
1296 // Delete for one of the forums for the first user.
1297 $firstcontext = reset($contexts);
1299 $deletedpostids = [];
1301 foreach ($postsbyforum as $user => $contexts) {
1302 foreach ($contexts as $thiscontextid => $theseposts) {
1303 $thesepostids = array_map(function($post) {
1307 if ($user == $user1->id && $thiscontextid == $firstcontext->id) {
1308 // This post is in the deleted context and by the target user.
1309 $deletedpostids = array_merge($deletedpostids, $thesepostids);
1311 // This post is by another user, or in a non-target context.
1312 $otherpostids = array_merge($otherpostids, $thesepostids);
1316 list($postinsql, $postinparams) = $DB->get_in_or_equal($deletedpostids, SQL_PARAMS_NAMED);
1317 list($otherpostinsql, $otherpostinparams) = $DB->get_in_or_equal($otherpostids, SQL_PARAMS_NAMED);
1319 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
1320 \core_user::get_user($user1->id),
1324 provider::delete_data_for_user($approvedcontextlist);
1326 // All posts should remain.
1327 $this->assertCount(40, $DB->get_records('forum_posts'));
1329 // There should be 8 posts belonging to user1.
1330 $this->assertCount(8, $DB->get_records('forum_posts', [
1331 'userid' => $user1->id,
1334 // Four of those posts should have been marked as deleted.
1335 // That means that the deleted flag is set, and both the subject and message are empty.
1336 $this->assertCount(4, $DB->get_records_select('forum_posts', "userid = :userid AND deleted = :deleted"
1337 . " AND " . $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject')
1338 . " AND " . $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message')
1340 'userid' => $user1->id,
1346 // Only user1's posts should have been marked this way.
1347 $this->assertCount(4, $DB->get_records('forum_posts', [
1350 $this->assertCount(4, $DB->get_records_select('forum_posts',
1351 $DB->sql_compare_text('subject') . " = " . $DB->sql_compare_text(':subject'), [
1354 $this->assertCount(4, $DB->get_records_select('forum_posts',
1355 $DB->sql_compare_text('message') . " = " . $DB->sql_compare_text(':message'), [
1359 // Only the posts in the first discussion should have been marked this way.
1360 $this->assertCount(4, $DB->get_records_select('forum_posts',
1361 "deleted = :deleted AND id {$postinsql}",
1362 array_merge($postinparams, [
1367 // Ratings should have been removed from the affected posts.
1368 $this->assertCount(0, $DB->get_records_select('rating', "itemid {$postinsql}", $postinparams));
1370 // Ratings should remain on posts in the other context, and posts not belonging to the affected user.
1371 $this->assertCount(144, $DB->get_records_select('rating', "itemid {$otherpostinsql}", $otherpostinparams));
1373 // Ratings should remain where the user has rated another person's post.
1374 $this->assertCount(32, $DB->get_records('rating', ['userid' => $user1->id]));
1376 // Tags for the affected posts should be removed.
1377 $this->assertCount(0, $DB->get_records_select('tag_instance', "itemid {$postinsql}", $postinparams));
1379 // Tags should remain for the other posts by this user, and all posts by other users.
1380 $this->assertCount(72, $DB->get_records_select('tag_instance', "itemid {$otherpostinsql}", $otherpostinparams));
1382 // Files for the affected posts should be removed.
1383 // 5 users * 2 forums * 1 file in each forum
1384 // Original total: 10
1385 // One post with file removed.
1386 $this->assertCount(0, $DB->get_records_select('files', "itemid {$postinsql}", $postinparams));
1388 // Files for the other posts should remain.
1389 $this->assertCount(18, $DB->get_records_select('files', "filename <> '.' AND itemid {$otherpostinsql}", $otherpostinparams));