<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace mod_forum; use mod_forum_tests_generator_trait; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/generator_trait.php'); /** * The exported_posts builder tests. * * @package mod_forum * @copyright 2019 Ryan Wyllie <ryan@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class builders_exported_posts_test extends \advanced_testcase { // Make use of the test generator trait. use mod_forum_tests_generator_trait; /** @var \mod_forum\local\builders\exported_posts */ private $builder; /** * Set up function for tests. */ public function setUp(): void { // We must clear the subscription caches. This has to be done both before each test, and after in case of other // tests using these functions. \mod_forum\subscriptions::reset_forum_cache(); $builderfactory = \mod_forum\local\container::get_builder_factory(); $this->builder = $builderfactory->get_exported_posts_builder(); } /** * Tear down function for tests. */ public function tearDown(): void { // We must clear the subscription caches. This has to be done both before each test, and after in case of other // tests using these functions. \mod_forum\subscriptions::reset_forum_cache(); } /** * Convert the stdClass values into their proper entity classes. * * @param stdClass[] $forums List of forums * @param stdClass[] $discussions List of discussions * @param stdClass[] $posts List of posts * @return array */ private function convert_to_entities(array $forums, array $discussions, array $posts) { global $DB; $entityfactory = \mod_forum\local\container::get_entity_factory(); return [ // Forums. array_map(function($forum) use ($entityfactory, $DB) { $course = $DB->get_record('course', ['id' => $forum->course]); $coursemodule = get_coursemodule_from_instance('forum', $forum->id); $context = \context_module::instance($coursemodule->id); return $entityfactory->get_forum_from_stdClass($forum, $context, $coursemodule, $course); }, $forums), // Discussions. array_map(function($discussion) use ($entityfactory) { return $entityfactory->get_discussion_from_stdClass($discussion); }, $discussions), // Posts. array_map(function($post) use ($entityfactory) { return $entityfactory->get_post_from_stdClass($post); }, $posts) ]; } /** * Test the build function throws exception if not given all of the forums for * the list of posts. */ public function test_build_throws_exception_on_missing_forums() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user = $datagenerator->create_user(); $course = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user); [$discussion2, $post2] = $this->helper_post_to_forum($forum2, $user); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2], [$post1, $post2] ); $this->expectException('moodle_exception'); $this->builder->build($user, [$forums[0]], $discussions, $posts); } /** * Test the build function throws exception if not given all of the discussions for * the list of posts. */ public function test_build_throws_exception_on_missing_discussions() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user = $datagenerator->create_user(); $course = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user); [$discussion2, $post2] = $this->helper_post_to_forum($forum2, $user); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2], [$post1, $post2] ); $this->expectException('moodle_exception'); $this->builder->build($user, $forums, [$discussions[0]], $posts); } /** * Test the build function returns the exported posts in the order that the posts are * given. */ public function test_build_returns_posts_in_order() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator();< $user1 = $datagenerator->create_user(); < $user2 = $datagenerator->create_user();$course = $datagenerator->create_course();> $user1 = $datagenerator->create_and_enrol($course); $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]); > $user2 = $datagenerator->create_and_enrol($course); $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]); >[$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); $post7 = $this->helper_reply_to_post($post2, $user1); $post8 = $this->helper_reply_to_post($post2, $user2); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8] ); // Randomly order the posts. shuffle($posts); $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); $expectedpostids = array_map(function($post) { return $post->get_id(); }, $posts); $actualpostids = array_map(function($exportedpost) { return (int) $exportedpost->id; }, $exportedposts); $this->assertEquals($expectedpostids, $actualpostids); } /** * Test the build function loads authors. */ public function test_build_loads_authors() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user1 = $datagenerator->create_user(); $user2 = $datagenerator->create_user(); $user3 = $datagenerator->create_user(); $course = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); // These 2 replies from user 3 won't be inlcuded in the export. $post7 = $this->helper_reply_to_post($post2, $user3); $post8 = $this->helper_reply_to_post($post2, $user3); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6] ); $datagenerator->enrol_user($user1->id, $course->id); $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); // We didn't include any posts from user 3 so we shouldn't see the authors // that match that user. $expectedids = [$user1->id, $user2->id]; $actualids = array_unique(array_map(function($exportedpost) { return (int) $exportedpost->author->id; }, $exportedposts)); sort($expectedids); sort($actualids); $this->assertEquals($expectedids, $actualids); } /** * Test the build function loads attachments. */ public function test_build_loads_attachments() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user1 = $datagenerator->create_user(); $user2 = $datagenerator->create_user(); $user3 = $datagenerator->create_user(); $course = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); $post7 = $this->helper_reply_to_post($post5, $user3); $post8 = $this->helper_reply_to_post($post5, $user3); $filestorage = get_file_storage(); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8] ); // Add an attachment to a post in forum 1. $attachment1 = $filestorage->create_file_from_string( [ 'contextid' => $forums[0]->get_context()->id, 'component' => 'mod_forum', 'filearea' => 'attachment', 'itemid' => $post1->id, 'filepath' => '/', 'filename' => 'example1.jpg', ], 'image contents' ); // Add an attachment to a post in forum 2. $attachment2 = $filestorage->create_file_from_string( [ 'contextid' => $forums[1]->get_context()->id, 'component' => 'mod_forum', 'filearea' => 'attachment', 'itemid' => $post7->id, 'filepath' => '/', 'filename' => 'example2.jpg', ], 'image contents' ); // Enrol the user so that they can see the posts. $datagenerator->enrol_user($user1->id, $course->id); $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); $expected = ['example1.jpg', 'example2.jpg']; $actual = array_reduce($exportedposts, function($carry, $exportedpost) { if (!empty($exportedpost->attachments)) { foreach ($exportedpost->attachments as $attachment) { $carry[] = $attachment->filename; } } return $carry; }, []); sort($expected); sort($actual); $this->assertEquals($expected, $actual); } /** * Test the build function loads author groups. */ public function test_build_loads_author_groups() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user1 = $datagenerator->create_user(); $user2 = $datagenerator->create_user(); $user3 = $datagenerator->create_user(); $course1 = $datagenerator->create_course(); $course2 = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); $post7 = $this->helper_reply_to_post($post5, $user3); $post8 = $this->helper_reply_to_post($post5, $user3); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8] ); // Enrol the user so that they can see the posts. $datagenerator->enrol_user($user1->id, $course1->id); $datagenerator->enrol_user($user1->id, $course2->id); $datagenerator->enrol_user($user2->id, $course1->id); $datagenerator->enrol_user($user2->id, $course2->id); $datagenerator->enrol_user($user3->id, $course1->id); $datagenerator->enrol_user($user3->id, $course2->id); $group1 = $datagenerator->create_group(['courseid' => $course1->id]); $group2 = $datagenerator->create_group(['courseid' => $course1->id]); // This group shouldn't be included in the results since it's in a different course. $group3 = $datagenerator->create_group(['courseid' => $course2->id]); $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group1->id]); $datagenerator->create_group_member(['userid' => $user2->id, 'groupid' => $group1->id]); $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group2->id]); $datagenerator->create_group_member(['userid' => $user1->id, 'groupid' => $group3->id]); $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); $expected = [ $user1->id => [$group1->id, $group2->id], $user2->id => [$group1->id], $user3->id => [] ]; $actual = array_reduce($exportedposts, function($carry, $exportedpost) { $author = $exportedpost->author; $authorid = $author->id; if (!isset($carry[$authorid])) { $carry[$authorid] = array_map(function($group) { return $group['id']; }, $author->groups); } return $carry; }, []); $this->assertEquals($expected, $actual); } /** * Test the build function loads tags. */ public function test_build_loads_tags() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user1 = $datagenerator->create_user(); $user2 = $datagenerator->create_user(); $user3 = $datagenerator->create_user(); $course1 = $datagenerator->create_course(); $course2 = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id]); $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); $post7 = $this->helper_reply_to_post($post5, $user3); $post8 = $this->helper_reply_to_post($post5, $user3); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8] ); // Enrol the user so that they can see the posts. $datagenerator->enrol_user($user1->id, $course1->id); $datagenerator->enrol_user($user1->id, $course2->id); $datagenerator->enrol_user($user2->id, $course1->id); $datagenerator->enrol_user($user2->id, $course2->id); $datagenerator->enrol_user($user3->id, $course1->id); $datagenerator->enrol_user($user3->id, $course2->id); \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post1->id, $forums[0]->get_context(), ['foo', 'bar']); \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post4->id, $forums[0]->get_context(), ['foo', 'baz']); \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post7->id, $forums[1]->get_context(), ['bip']); $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); $expected = [ $post1->id => ['foo', 'bar'], $post4->id => ['foo', 'baz'], $post7->id => ['bip'] ]; $actual = array_reduce($exportedposts, function($carry, $exportedpost) { if (!empty($exportedpost->tags)) { $carry[$exportedpost->id] = array_map(function($tag) { return $tag['displayname']; }, $exportedpost->tags); } return $carry; }, []); $this->assertEquals($expected, $actual); } /** * Test the build function loads read_receipts. */ public function test_build_loads_read_receipts() { $this->resetAfterTest(); $datagenerator = $this->getDataGenerator(); $user1 = $datagenerator->create_user(['trackforums' => 1]); $user2 = $datagenerator->create_user(['trackforums' => 0]); $course1 = $datagenerator->create_course(); $course2 = $datagenerator->create_course(); $forum1 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OPTIONAL]); $forum2 = $datagenerator->create_module('forum', ['course' => $course1->id, 'trackingtype' => FORUM_TRACKING_OFF]); [$discussion1, $post1] = $this->helper_post_to_forum($forum1, $user1); [$discussion2, $post2] = $this->helper_post_to_forum($forum1, $user2); $post3 = $this->helper_reply_to_post($post1, $user1); $post4 = $this->helper_reply_to_post($post1, $user2); [$discussion3, $post5] = $this->helper_post_to_forum($forum2, $user1); [$discussion4, $post6] = $this->helper_post_to_forum($forum2, $user2); $post7 = $this->helper_reply_to_post($post5, $user1); $post8 = $this->helper_reply_to_post($post5, $user1); [$forums, $discussions, $posts] = $this->convert_to_entities( [$forum1, $forum2], [$discussion1, $discussion2, $discussion3, $discussion4], [$post1, $post2, $post3, $post4, $post5, $post6, $post7, $post8] ); // Enrol the user so that they can see the posts. $datagenerator->enrol_user($user1->id, $course1->id); $datagenerator->enrol_user($user1->id, $course2->id); $datagenerator->enrol_user($user2->id, $course1->id); $datagenerator->enrol_user($user2->id, $course2->id); forum_tp_add_read_record($user1->id, $post1->id); forum_tp_add_read_record($user1->id, $post4->id); forum_tp_add_read_record($user1->id, $post7->id); forum_tp_add_read_record($user2->id, $post1->id); forum_tp_add_read_record($user2->id, $post4->id); forum_tp_add_read_record($user2->id, $post7->id); // User 1 has tracking enabled. $exportedposts = $this->builder->build($user1, $forums, $discussions, $posts); $expected = [ // Tracking set for forum 1 for user 1. $post1->id => false, $post2->id => true, $post3->id => true, $post4->id => false, // Tracking is off for forum 2 so everything should be null. $post5->id => null, $post6->id => null, $post7->id => null, $post8->id => null ]; $actual = array_reduce($exportedposts, function($carry, $exportedpost) { $carry[$exportedpost->id] = $exportedpost->unread; return $carry; }, []); $this->assertEquals($expected, $actual); // User 2 has tracking disabled. $exportedposts = $this->builder->build($user2, $forums, $discussions, $posts); // Tracking is off for user 2 so everything should be null. $expected = [ $post1->id => null, $post2->id => null, $post3->id => null, $post4->id => null, $post5->id => null, $post6->id => null, $post7->id => null, $post8->id => null ]; $actual = array_reduce($exportedposts, function($carry, $exportedpost) { $carry[$exportedpost->id] = $exportedpost->unread; return $carry; }, []); $this->assertEquals($expected, $actual); } }