Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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  namespace mod_forum;
  18  
  19  use mod_forum_tests_generator_trait;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  require_once (__DIR__ . '/generator_trait.php');
  24  
  25  /**
  26   * The exported_posts builder tests.
  27   *
  28   * @package    mod_forum
  29   * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
  30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  class builders_exported_posts_test extends \advanced_testcase {
  33      // Make use of the test generator trait.
  34      use mod_forum_tests_generator_trait;
  35  
  36      /** @var \mod_forum\local\builders\exported_posts */
  37      private $builder;
  38  
  39      /**
  40       * Set up function for tests.
  41       */
  42      public function setUp(): void {
  43          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  44          // tests using these functions.
  45          \mod_forum\subscriptions::reset_forum_cache();
  46  
  47          $builderfactory = \mod_forum\local\container::get_builder_factory();
  48          $this->builder = $builderfactory->get_exported_posts_builder();
  49      }
  50  
  51      /**
  52       * Tear down function for tests.
  53       */
  54      public function tearDown(): void {
  55          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
  56          // tests using these functions.
  57          \mod_forum\subscriptions::reset_forum_cache();
  58      }
  59  
  60      /**
  61       * Convert the stdClass values into their proper entity classes.
  62       *
  63       * @param stdClass[] $forums List of forums
  64       * @param stdClass[] $discussions List of discussions
  65       * @param stdClass[] $posts List of posts
  66       * @return array
  67       */
  68      private function convert_to_entities(array $forums, array $discussions, array $posts) {
  69          global $DB;
  70          $entityfactory = \mod_forum\local\container::get_entity_factory();
  71  
  72          return [
  73              // Forums.
  74              array_map(function($forum) use ($entityfactory, $DB) {
  75                  $course = $DB->get_record('course', ['id' => $forum->course]);
  76                  $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
  77                  $context = \context_module::instance($coursemodule->id);
  78                  return $entityfactory->get_forum_from_stdClass($forum, $context, $coursemodule, $course);
  79              }, $forums),
  80              // Discussions.
  81              array_map(function($discussion) use ($entityfactory) {
  82                  return $entityfactory->get_discussion_from_stdClass($discussion);
  83              }, $discussions),
  84              // Posts.
  85              array_map(function($post) use ($entityfactory) {
  86                  return $entityfactory->get_post_from_stdClass($post);
  87              }, $posts)
  88          ];
  89      }
  90  
  91      /**
  92       * Test the build function throws exception if not given all of the forums for
  93       * the list of posts.
  94       */
  95      public function test_build_throws_exception_on_missing_forums() {
  96          $this->resetAfterTest();
  97  
  98          $datagenerator = $this->getDataGenerator();
  99          $user = $datagenerator->create_user();
 100          $course = $datagenerator->create_course();
 101          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 102          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 103          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user);
 104          [$discussion2, $post2] = $this->helper_post_to_forum($forum2, $user);
 105  
 106          [$forums, $discussions, $posts] = $this->convert_to_entities(
 107              [$forum1, $forum2],
 108              [$discussion1, $discussion2],
 109              [$post1, $post2]
 110          );
 111  
 112          $this->expectException('moodle_exception');
 113          $this->builder->build($user, [$forums[0]], $discussions, $posts);
 114      }
 115  
 116      /**
 117       * Test the build function throws exception if not given all of the discussions for
 118       * the list of posts.
 119       */
 120      public function test_build_throws_exception_on_missing_discussions() {
 121          $this->resetAfterTest();
 122  
 123          $datagenerator = $this->getDataGenerator();
 124          $user = $datagenerator->create_user();
 125          $course = $datagenerator->create_course();
 126          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 127          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 128          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user);
 129          [$discussion2, $post2] = $this->helper_post_to_forum($forum2, $user);
 130  
 131          [$forums, $discussions, $posts] = $this->convert_to_entities(
 132              [$forum1, $forum2],
 133              [$discussion1, $discussion2],
 134              [$post1, $post2]
 135          );
 136  
 137          $this->expectException('moodle_exception');
 138          $this->builder->build($user, $forums, [$discussions[0]], $posts);
 139      }
 140  
 141      /**
 142       * Test the build function returns the exported posts in the order that the posts are
 143       * given.
 144       */
 145      public function test_build_returns_posts_in_order() {
 146          $this->resetAfterTest();
 147  
 148          $datagenerator = $this->getDataGenerator();
 149          $course = $datagenerator->create_course();
 150          $user1 = $datagenerator->create_and_enrol($course);
 151          $user2 = $datagenerator->create_and_enrol($course);
 152  
 153          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 154          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 155          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 156          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 157          $post3 = $this->helper_reply_to_post($post1, $user1);
 158          $post4 = $this->helper_reply_to_post($post1, $user2);
 159          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 160          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 161          $post7 = $this->helper_reply_to_post($post2, $user1);
 162          $post8 = $this->helper_reply_to_post($post2, $user2);
 163  
 164          [$forums, $discussions, $posts] = $this->convert_to_entities(
 165              [$forum1, $forum2],
 166              [$discussion1, $discussion2, $discussion3, $discussion4],
 167              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 168          );
 169  
 170          // Randomly order the posts.
 171          shuffle($posts);
 172  
 173          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 174  
 175          $expectedpostids = array_map(function($post) {
 176              return $post->get_id();
 177          }, $posts);
 178          $actualpostids = array_map(function($exportedpost) {
 179              return (int) $exportedpost->id;
 180          }, $exportedposts);
 181  
 182          $this->assertEquals($expectedpostids, $actualpostids);
 183      }
 184  
 185      /**
 186       * Test the build function loads authors.
 187       */
 188      public function test_build_loads_authors() {
 189          $this->resetAfterTest();
 190  
 191          $datagenerator = $this->getDataGenerator();
 192          $user1 = $datagenerator->create_user();
 193          $user2 = $datagenerator->create_user();
 194          $user3 = $datagenerator->create_user();
 195          $course = $datagenerator->create_course();
 196          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 197          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 198          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 199          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 200          $post3 = $this->helper_reply_to_post($post1, $user1);
 201          $post4 = $this->helper_reply_to_post($post1, $user2);
 202          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 203          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 204          // These 2 replies from user 3 won't be inlcuded in the export.
 205          $post7 = $this->helper_reply_to_post($post2, $user3);
 206          $post8 = $this->helper_reply_to_post($post2, $user3);
 207  
 208          [$forums, $discussions, $posts] = $this->convert_to_entities(
 209              [$forum1, $forum2],
 210              [$discussion1, $discussion2, $discussion3, $discussion4],
 211              [$post1, $post2, $post3, $post4, $post5, $post6]
 212          );
 213  
 214          $datagenerator->enrol_user($user1->id, $course->id);
 215          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 216  
 217          // We didn't include any posts from user 3 so we shouldn't see the authors
 218          // that match that user.
 219          $expectedids = [$user1->id, $user2->id];
 220          $actualids = array_unique(array_map(function($exportedpost) {
 221              return (int) $exportedpost->author->id;
 222          }, $exportedposts));
 223  
 224          sort($expectedids);
 225          sort($actualids);
 226  
 227          $this->assertEquals($expectedids, $actualids);
 228      }
 229  
 230      /**
 231       * Test the build function loads attachments.
 232       */
 233      public function test_build_loads_attachments() {
 234          $this->resetAfterTest();
 235  
 236          $datagenerator = $this->getDataGenerator();
 237          $user1 = $datagenerator->create_user();
 238          $user2 = $datagenerator->create_user();
 239          $user3 = $datagenerator->create_user();
 240          $course = $datagenerator->create_course();
 241          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 242          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 243          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 244          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 245          $post3 = $this->helper_reply_to_post($post1, $user1);
 246          $post4 = $this->helper_reply_to_post($post1, $user2);
 247          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 248          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 249          $post7 = $this->helper_reply_to_post($post5, $user3);
 250          $post8 = $this->helper_reply_to_post($post5, $user3);
 251          $filestorage = get_file_storage();
 252  
 253          [$forums, $discussions, $posts] = $this->convert_to_entities(
 254              [$forum1, $forum2],
 255              [$discussion1, $discussion2, $discussion3, $discussion4],
 256              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 257          );
 258  
 259          // Add an attachment to a post in forum 1.
 260          $attachment1 = $filestorage->create_file_from_string(
 261              [
 262                  'contextid' => $forums[0]->get_context()->id,
 263                  'component' => 'mod_forum',
 264                  'filearea'  => 'attachment',
 265                  'itemid'    => $post1->id,
 266                  'filepath'  => '/',
 267                  'filename'  => 'example1.jpg',
 268              ],
 269              'image contents'
 270          );
 271  
 272          // Add an attachment to a post in forum 2.
 273          $attachment2 = $filestorage->create_file_from_string(
 274              [
 275                  'contextid' => $forums[1]->get_context()->id,
 276                  'component' => 'mod_forum',
 277                  'filearea'  => 'attachment',
 278                  'itemid'    => $post7->id,
 279                  'filepath'  => '/',
 280                  'filename'  => 'example2.jpg',
 281              ],
 282              'image contents'
 283          );
 284  
 285          // Enrol the user so that they can see the posts.
 286          $datagenerator->enrol_user($user1->id, $course->id);
 287  
 288          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 289  
 290          $expected = ['example1.jpg', 'example2.jpg'];
 291          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 292              if (!empty($exportedpost->attachments)) {
 293                  foreach ($exportedpost->attachments as $attachment) {
 294                      $carry[] = $attachment->filename;
 295                  }
 296              }
 297              return $carry;
 298          }, []);
 299  
 300          sort($expected);
 301          sort($actual);
 302  
 303          $this->assertEquals($expected, $actual);
 304      }
 305  
 306      /**
 307       * Test the build function loads author groups.
 308       */
 309      public function test_build_loads_author_groups() {
 310          $this->resetAfterTest();
 311  
 312          $datagenerator = $this->getDataGenerator();
 313          $user1 = $datagenerator->create_user();
 314          $user2 = $datagenerator->create_user();
 315          $user3 = $datagenerator->create_user();
 316          $course1 = $datagenerator->create_course();
 317          $course2 = $datagenerator->create_course();
 318          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 319          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 320          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 321          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 322          $post3 = $this->helper_reply_to_post($post1, $user1);
 323          $post4 = $this->helper_reply_to_post($post1, $user2);
 324          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 325          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 326          $post7 = $this->helper_reply_to_post($post5, $user3);
 327          $post8 = $this->helper_reply_to_post($post5, $user3);
 328  
 329          [$forums, $discussions, $posts] = $this->convert_to_entities(
 330              [$forum1, $forum2],
 331              [$discussion1, $discussion2, $discussion3, $discussion4],
 332              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 333          );
 334  
 335          // Enrol the user so that they can see the posts.
 336          $datagenerator->enrol_user($user1->id, $course1->id);
 337          $datagenerator->enrol_user($user1->id, $course2->id);
 338          $datagenerator->enrol_user($user2->id, $course1->id);
 339          $datagenerator->enrol_user($user2->id, $course2->id);
 340          $datagenerator->enrol_user($user3->id, $course1->id);
 341          $datagenerator->enrol_user($user3->id, $course2->id);
 342  
 343          $group1 = $datagenerator->create_group(['courseid' => $course1->id]);
 344          $group2 = $datagenerator->create_group(['courseid' => $course1->id]);
 345          // This group shouldn't be included in the results since it's in a different course.
 346          $group3 = $datagenerator->create_group(['courseid' => $course2->id]);
 347  
 348          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group1->id]);
 349          $datagenerator->create_group_member(['userid' => $user2->id, 'groupid' => $group1->id]);
 350          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group2->id]);
 351          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group3->id]);
 352  
 353          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 354  
 355          $expected = [
 356              $user1->id => [$group1->id, $group2->id],
 357              $user2->id => [$group1->id],
 358              $user3->id => []
 359          ];
 360          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 361              $author = $exportedpost->author;
 362              $authorid = $author->id;
 363  
 364              if (!isset($carry[$authorid])) {
 365                  $carry[$authorid] = array_map(function($group) {
 366                      return $group['id'];
 367                  }, $author->groups);
 368              }
 369  
 370              return $carry;
 371          }, []);
 372  
 373          $this->assertEquals($expected, $actual);
 374      }
 375  
 376      /**
 377       * Test the build function loads tags.
 378       */
 379      public function test_build_loads_tags() {
 380          $this->resetAfterTest();
 381  
 382          $datagenerator = $this->getDataGenerator();
 383          $user1 = $datagenerator->create_user();
 384          $user2 = $datagenerator->create_user();
 385          $user3 = $datagenerator->create_user();
 386          $course1 = $datagenerator->create_course();
 387          $course2 = $datagenerator->create_course();
 388          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 389          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 390          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 391          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 392          $post3 = $this->helper_reply_to_post($post1, $user1);
 393          $post4 = $this->helper_reply_to_post($post1, $user2);
 394          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 395          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 396          $post7 = $this->helper_reply_to_post($post5, $user3);
 397          $post8 = $this->helper_reply_to_post($post5, $user3);
 398  
 399          [$forums, $discussions, $posts] = $this->convert_to_entities(
 400              [$forum1, $forum2],
 401              [$discussion1, $discussion2, $discussion3, $discussion4],
 402              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 403          );
 404  
 405          // Enrol the user so that they can see the posts.
 406          $datagenerator->enrol_user($user1->id, $course1->id);
 407          $datagenerator->enrol_user($user1->id, $course2->id);
 408          $datagenerator->enrol_user($user2->id, $course1->id);
 409          $datagenerator->enrol_user($user2->id, $course2->id);
 410          $datagenerator->enrol_user($user3->id, $course1->id);
 411          $datagenerator->enrol_user($user3->id, $course2->id);
 412  
 413          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post1->id, $forums[0]->get_context(), ['foo', 'bar']);
 414          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post4->id, $forums[0]->get_context(), ['foo', 'baz']);
 415          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post7->id, $forums[1]->get_context(), ['bip']);
 416  
 417          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 418  
 419          $expected = [
 420              $post1->id => ['foo', 'bar'],
 421              $post4->id => ['foo', 'baz'],
 422              $post7->id => ['bip']
 423          ];
 424          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 425              if (!empty($exportedpost->tags)) {
 426                  $carry[$exportedpost->id] = array_map(function($tag) {
 427                      return $tag['displayname'];
 428                  }, $exportedpost->tags);
 429              }
 430  
 431              return $carry;
 432          }, []);
 433  
 434          $this->assertEquals($expected, $actual);
 435      }
 436  
 437      /**
 438       * Test the build function loads read_receipts.
 439       */
 440      public function test_build_loads_read_receipts() {
 441          $this->resetAfterTest();
 442  
 443          $datagenerator = $this->getDataGenerator();
 444          $user1 = $datagenerator->create_user(['trackforums' => 1]);
 445          $user2 = $datagenerator->create_user(['trackforums' => 0]);
 446          $course1 = $datagenerator->create_course();
 447          $course2 = $datagenerator->create_course();
 448          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL]);
 449          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OFF]);
 450          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 451          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 452          $post3 = $this->helper_reply_to_post($post1, $user1);
 453          $post4 = $this->helper_reply_to_post($post1, $user2);
 454          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 455          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 456          $post7 = $this->helper_reply_to_post($post5, $user1);
 457          $post8 = $this->helper_reply_to_post($post5, $user1);
 458  
 459          [$forums, $discussions, $posts] = $this->convert_to_entities(
 460              [$forum1, $forum2],
 461              [$discussion1, $discussion2, $discussion3, $discussion4],
 462              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 463          );
 464  
 465          // Enrol the user so that they can see the posts.
 466          $datagenerator->enrol_user($user1->id, $course1->id);
 467          $datagenerator->enrol_user($user1->id, $course2->id);
 468          $datagenerator->enrol_user($user2->id, $course1->id);
 469          $datagenerator->enrol_user($user2->id, $course2->id);
 470  
 471          forum_tp_add_read_record($user1->id, $post1->id);
 472          forum_tp_add_read_record($user1->id, $post4->id);
 473          forum_tp_add_read_record($user1->id, $post7->id);
 474          forum_tp_add_read_record($user2->id, $post1->id);
 475          forum_tp_add_read_record($user2->id, $post4->id);
 476          forum_tp_add_read_record($user2->id, $post7->id);
 477  
 478          // User 1 has tracking enabled.
 479          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 480  
 481          $expected = [
 482              // Tracking set for forum 1 for user 1.
 483              $post1->id => false,
 484              $post2->id => true,
 485              $post3->id => true,
 486              $post4->id => false,
 487              // Tracking is off for forum 2 so everything should be null.
 488              $post5->id => null,
 489              $post6->id => null,
 490              $post7->id => null,
 491              $post8->id => null
 492          ];
 493          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 494              $carry[$exportedpost->id] = $exportedpost->unread;
 495              return $carry;
 496          }, []);
 497  
 498          $this->assertEquals($expected, $actual);
 499  
 500          // User 2 has tracking disabled.
 501          $exportedposts = $this->builder->build($user2, $forums, $discussions, $posts);
 502  
 503          // Tracking is off for user 2 so everything should be null.
 504          $expected = [
 505              $post1->id => null,
 506              $post2->id => null,
 507              $post3->id => null,
 508              $post4->id => null,
 509              $post5->id => null,
 510              $post6->id => null,
 511              $post7->id => null,
 512              $post8->id => null
 513          ];
 514          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 515              $carry[$exportedpost->id] = $exportedpost->unread;
 516              return $carry;
 517          }, []);
 518  
 519          $this->assertEquals($expected, $actual);
 520      }
 521  }