Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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