Search moodle.org's
Developer Documentation

  • 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 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [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  /**
      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  }