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