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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body