Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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          $user1 = $datagenerator->create_user();
 150          $user2 = $datagenerator->create_user();
 151          $course = $datagenerator->create_course();
 152          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 153          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 154          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 155          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 156          $post3 = $this->helper_reply_to_post($post1, $user1);
 157          $post4 = $this->helper_reply_to_post($post1, $user2);
 158          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 159          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 160          $post7 = $this->helper_reply_to_post($post2, $user1);
 161          $post8 = $this->helper_reply_to_post($post2, $user2);
 162  
 163          [$forums, $discussions, $posts] = $this->convert_to_entities(
 164              [$forum1, $forum2],
 165              [$discussion1, $discussion2, $discussion3, $discussion4],
 166              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 167          );
 168  
 169          // Randomly order the posts.
 170          shuffle($posts);
 171  
 172          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 173  
 174          $expectedpostids = array_map(function($post) {
 175              return $post->get_id();
 176          }, $posts);
 177          $actualpostids = array_map(function($exportedpost) {
 178              return (int) $exportedpost->id;
 179          }, $exportedposts);
 180  
 181          $this->assertEquals($expectedpostids, $actualpostids);
 182      }
 183  
 184      /**
 185       * Test the build function loads authors.
 186       */
 187      public function test_build_loads_authors() {
 188          $this->resetAfterTest();
 189  
 190          $datagenerator = $this->getDataGenerator();
 191          $user1 = $datagenerator->create_user();
 192          $user2 = $datagenerator->create_user();
 193          $user3 = $datagenerator->create_user();
 194          $course = $datagenerator->create_course();
 195          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 196          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 197          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 198          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 199          $post3 = $this->helper_reply_to_post($post1, $user1);
 200          $post4 = $this->helper_reply_to_post($post1, $user2);
 201          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 202          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 203          // These 2 replies from user 3 won't be inlcuded in the export.
 204          $post7 = $this->helper_reply_to_post($post2, $user3);
 205          $post8 = $this->helper_reply_to_post($post2, $user3);
 206  
 207          [$forums, $discussions, $posts] = $this->convert_to_entities(
 208              [$forum1, $forum2],
 209              [$discussion1, $discussion2, $discussion3, $discussion4],
 210              [$post1, $post2, $post3, $post4, $post5, $post6]
 211          );
 212  
 213          $datagenerator->enrol_user($user1->id, $course->id);
 214          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 215  
 216          // We didn't include any posts from user 3 so we shouldn't see the authors
 217          // that match that user.
 218          $expectedids = [$user1->id, $user2->id];
 219          $actualids = array_unique(array_map(function($exportedpost) {
 220              return (int) $exportedpost->author->id;
 221          }, $exportedposts));
 222  
 223          sort($expectedids);
 224          sort($actualids);
 225  
 226          $this->assertEquals($expectedids, $actualids);
 227      }
 228  
 229      /**
 230       * Test the build function loads attachments.
 231       */
 232      public function test_build_loads_attachments() {
 233          $this->resetAfterTest();
 234  
 235          $datagenerator = $this->getDataGenerator();
 236          $user1 = $datagenerator->create_user();
 237          $user2 = $datagenerator->create_user();
 238          $user3 = $datagenerator->create_user();
 239          $course = $datagenerator->create_course();
 240          $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]);
 241          $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]);
 242          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 243          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 244          $post3 = $this->helper_reply_to_post($post1, $user1);
 245          $post4 = $this->helper_reply_to_post($post1, $user2);
 246          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 247          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 248          $post7 = $this->helper_reply_to_post($post5, $user3);
 249          $post8 = $this->helper_reply_to_post($post5, $user3);
 250          $filestorage = get_file_storage();
 251  
 252          [$forums, $discussions, $posts] = $this->convert_to_entities(
 253              [$forum1, $forum2],
 254              [$discussion1, $discussion2, $discussion3, $discussion4],
 255              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 256          );
 257  
 258          // Add an attachment to a post in forum 1.
 259          $attachment1 = $filestorage->create_file_from_string(
 260              [
 261                  'contextid' => $forums[0]->get_context()->id,
 262                  'component' => 'mod_forum',
 263                  'filearea'  => 'attachment',
 264                  'itemid'    => $post1->id,
 265                  'filepath'  => '/',
 266                  'filename'  => 'example1.jpg',
 267              ],
 268              'image contents'
 269          );
 270  
 271          // Add an attachment to a post in forum 2.
 272          $attachment2 = $filestorage->create_file_from_string(
 273              [
 274                  'contextid' => $forums[1]->get_context()->id,
 275                  'component' => 'mod_forum',
 276                  'filearea'  => 'attachment',
 277                  'itemid'    => $post7->id,
 278                  'filepath'  => '/',
 279                  'filename'  => 'example2.jpg',
 280              ],
 281              'image contents'
 282          );
 283  
 284          // Enrol the user so that they can see the posts.
 285          $datagenerator->enrol_user($user1->id, $course->id);
 286  
 287          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 288  
 289          $expected = ['example1.jpg', 'example2.jpg'];
 290          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 291              if (!empty($exportedpost->attachments)) {
 292                  foreach ($exportedpost->attachments as $attachment) {
 293                      $carry[] = $attachment->filename;
 294                  }
 295              }
 296              return $carry;
 297          }, []);
 298  
 299          sort($expected);
 300          sort($actual);
 301  
 302          $this->assertEquals($expected, $actual);
 303      }
 304  
 305      /**
 306       * Test the build function loads author groups.
 307       */
 308      public function test_build_loads_author_groups() {
 309          $this->resetAfterTest();
 310  
 311          $datagenerator = $this->getDataGenerator();
 312          $user1 = $datagenerator->create_user();
 313          $user2 = $datagenerator->create_user();
 314          $user3 = $datagenerator->create_user();
 315          $course1 = $datagenerator->create_course();
 316          $course2 = $datagenerator->create_course();
 317          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 318          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 319          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 320          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 321          $post3 = $this->helper_reply_to_post($post1, $user1);
 322          $post4 = $this->helper_reply_to_post($post1, $user2);
 323          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 324          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 325          $post7 = $this->helper_reply_to_post($post5, $user3);
 326          $post8 = $this->helper_reply_to_post($post5, $user3);
 327  
 328          [$forums, $discussions, $posts] = $this->convert_to_entities(
 329              [$forum1, $forum2],
 330              [$discussion1, $discussion2, $discussion3, $discussion4],
 331              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 332          );
 333  
 334          // Enrol the user so that they can see the posts.
 335          $datagenerator->enrol_user($user1->id, $course1->id);
 336          $datagenerator->enrol_user($user1->id, $course2->id);
 337          $datagenerator->enrol_user($user2->id, $course1->id);
 338          $datagenerator->enrol_user($user2->id, $course2->id);
 339          $datagenerator->enrol_user($user3->id, $course1->id);
 340          $datagenerator->enrol_user($user3->id, $course2->id);
 341  
 342          $group1 = $datagenerator->create_group(['courseid' => $course1->id]);
 343          $group2 = $datagenerator->create_group(['courseid' => $course1->id]);
 344          // This group shouldn't be included in the results since it's in a different course.
 345          $group3 = $datagenerator->create_group(['courseid' => $course2->id]);
 346  
 347          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group1->id]);
 348          $datagenerator->create_group_member(['userid' => $user2->id, 'groupid' => $group1->id]);
 349          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group2->id]);
 350          $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group3->id]);
 351  
 352          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 353  
 354          $expected = [
 355              $user1->id => [$group1->id, $group2->id],
 356              $user2->id => [$group1->id],
 357              $user3->id => []
 358          ];
 359          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 360              $author = $exportedpost->author;
 361              $authorid = $author->id;
 362  
 363              if (!isset($carry[$authorid])) {
 364                  $carry[$authorid] = array_map(function($group) {
 365                      return $group['id'];
 366                  }, $author->groups);
 367              }
 368  
 369              return $carry;
 370          }, []);
 371  
 372          $this->assertEquals($expected, $actual);
 373      }
 374  
 375      /**
 376       * Test the build function loads tags.
 377       */
 378      public function test_build_loads_tags() {
 379          $this->resetAfterTest();
 380  
 381          $datagenerator = $this->getDataGenerator();
 382          $user1 = $datagenerator->create_user();
 383          $user2 = $datagenerator->create_user();
 384          $user3 = $datagenerator->create_user();
 385          $course1 = $datagenerator->create_course();
 386          $course2 = $datagenerator->create_course();
 387          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 388          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]);
 389          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 390          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 391          $post3 = $this->helper_reply_to_post($post1, $user1);
 392          $post4 = $this->helper_reply_to_post($post1, $user2);
 393          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 394          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 395          $post7 = $this->helper_reply_to_post($post5, $user3);
 396          $post8 = $this->helper_reply_to_post($post5, $user3);
 397  
 398          [$forums, $discussions, $posts] = $this->convert_to_entities(
 399              [$forum1, $forum2],
 400              [$discussion1, $discussion2, $discussion3, $discussion4],
 401              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 402          );
 403  
 404          // Enrol the user so that they can see the posts.
 405          $datagenerator->enrol_user($user1->id, $course1->id);
 406          $datagenerator->enrol_user($user1->id, $course2->id);
 407          $datagenerator->enrol_user($user2->id, $course1->id);
 408          $datagenerator->enrol_user($user2->id, $course2->id);
 409          $datagenerator->enrol_user($user3->id, $course1->id);
 410          $datagenerator->enrol_user($user3->id, $course2->id);
 411  
 412          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post1->id, $forums[0]->get_context(), ['foo', 'bar']);
 413          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post4->id, $forums[0]->get_context(), ['foo', 'baz']);
 414          \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post7->id, $forums[1]->get_context(), ['bip']);
 415  
 416          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 417  
 418          $expected = [
 419              $post1->id => ['foo', 'bar'],
 420              $post4->id => ['foo', 'baz'],
 421              $post7->id => ['bip']
 422          ];
 423          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 424              if (!empty($exportedpost->tags)) {
 425                  $carry[$exportedpost->id] = array_map(function($tag) {
 426                      return $tag['displayname'];
 427                  }, $exportedpost->tags);
 428              }
 429  
 430              return $carry;
 431          }, []);
 432  
 433          $this->assertEquals($expected, $actual);
 434      }
 435  
 436      /**
 437       * Test the build function loads read_receipts.
 438       */
 439      public function test_build_loads_read_receipts() {
 440          $this->resetAfterTest();
 441  
 442          $datagenerator = $this->getDataGenerator();
 443          $user1 = $datagenerator->create_user(['trackforums' => 1]);
 444          $user2 = $datagenerator->create_user(['trackforums' => 0]);
 445          $course1 = $datagenerator->create_course();
 446          $course2 = $datagenerator->create_course();
 447          $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL]);
 448          $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OFF]);
 449          [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1);
 450          [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2);
 451          $post3 = $this->helper_reply_to_post($post1, $user1);
 452          $post4 = $this->helper_reply_to_post($post1, $user2);
 453          [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1);
 454          [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2);
 455          $post7 = $this->helper_reply_to_post($post5, $user1);
 456          $post8 = $this->helper_reply_to_post($post5, $user1);
 457  
 458          [$forums, $discussions, $posts] = $this->convert_to_entities(
 459              [$forum1, $forum2],
 460              [$discussion1, $discussion2, $discussion3, $discussion4],
 461              [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8]
 462          );
 463  
 464          // Enrol the user so that they can see the posts.
 465          $datagenerator->enrol_user($user1->id, $course1->id);
 466          $datagenerator->enrol_user($user1->id, $course2->id);
 467          $datagenerator->enrol_user($user2->id, $course1->id);
 468          $datagenerator->enrol_user($user2->id, $course2->id);
 469  
 470          forum_tp_add_read_record($user1->id, $post1->id);
 471          forum_tp_add_read_record($user1->id, $post4->id);
 472          forum_tp_add_read_record($user1->id, $post7->id);
 473          forum_tp_add_read_record($user2->id, $post1->id);
 474          forum_tp_add_read_record($user2->id, $post4->id);
 475          forum_tp_add_read_record($user2->id, $post7->id);
 476  
 477          // User 1 has tracking enabled.
 478          $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts);
 479  
 480          $expected = [
 481              // Tracking set for forum 1 for user 1.
 482              $post1->id => false,
 483              $post2->id => true,
 484              $post3->id => true,
 485              $post4->id => false,
 486              // Tracking is off for forum 2 so everything should be null.
 487              $post5->id => null,
 488              $post6->id => null,
 489              $post7->id => null,
 490              $post8->id => null
 491          ];
 492          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 493              $carry[$exportedpost->id] = $exportedpost->unread;
 494              return $carry;
 495          }, []);
 496  
 497          $this->assertEquals($expected, $actual);
 498  
 499          // User 2 has tracking disabled.
 500          $exportedposts = $this->builder->build($user2, $forums, $discussions, $posts);
 501  
 502          // Tracking is off for user 2 so everything should be null.
 503          $expected = [
 504              $post1->id => null,
 505              $post2->id => null,
 506              $post3->id => null,
 507              $post4->id => null,
 508              $post5->id => null,
 509              $post6->id => null,
 510              $post7->id => null,
 511              $post8->id => null
 512          ];
 513          $actual = array_reduce($exportedposts, function($carry, $exportedpost) {
 514              $carry[$exportedpost->id] = $exportedpost->unread;
 515              return $carry;
 516          }, []);
 517  
 518          $this->assertEquals($expected, $actual);
 519      }
 520  }