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 310 and 311] [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  namespace mod_forum;
      18  
      19  use externallib_advanced_testcase;
      20  use mod_forum_external;
      21  
      22  defined('MOODLE_INTERNAL') || die();
      23  
      24  global $CFG;
      25  
      26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
      27  require_once($CFG->dirroot . '/mod/forum/lib.php');
      28  
      29  /**
      30   * The module forums external functions unit tests
      31   *
      32   * @package    mod_forum
      33   * @category   external
      34   * @copyright  2012 Mark Nelson <markn@moodle.com>
      35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      36   */
      37  class externallib_test extends externallib_advanced_testcase {
      38  
      39      /**
      40       * Tests set up
      41       */
      42      protected function setUp(): void {
      43          global $CFG;
      44  
      45          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
      46          // tests using these functions.
      47          \mod_forum\subscriptions::reset_forum_cache();
      48  
      49          require_once($CFG->dirroot . '/mod/forum/externallib.php');
      50      }
      51  
      52      public function tearDown(): void {
      53          // We must clear the subscription caches. This has to be done both before each test, and after in case of other
      54          // tests using these functions.
      55          \mod_forum\subscriptions::reset_forum_cache();
      56      }
      57  
      58      /**
      59       * Test get forums
      60       */
      61      public function test_mod_forum_get_forums_by_courses() {
      62          global $USER, $CFG, $DB;
      63  
      64          $this->resetAfterTest(true);
      65  
      66          // Create a user.
      67          $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
      68  
      69          // Set to the user.
      70          self::setUser($user);
      71  
      72          // Create courses to add the modules.
      73          $course1 = self::getDataGenerator()->create_course();
      74          $course2 = self::getDataGenerator()->create_course();
      75  
      76          // First forum.
      77          $record = new \stdClass();
      78          $record->introformat = FORMAT_HTML;
      79          $record->course = $course1->id;
      80          $record->trackingtype = FORUM_TRACKING_FORCED;
      81          $forum1 = self::getDataGenerator()->create_module('forum', $record);
      82  
      83          // Second forum.
      84          $record = new \stdClass();
      85          $record->introformat = FORMAT_HTML;
      86          $record->course = $course2->id;
      87          $record->trackingtype = FORUM_TRACKING_OFF;
      88          $forum2 = self::getDataGenerator()->create_module('forum', $record);
      89          $forum2->introfiles = [];
      90  
      91          // Add discussions to the forums.
      92          $record = new \stdClass();
      93          $record->course = $course1->id;
      94          $record->userid = $user->id;
      95          $record->forum = $forum1->id;
      96          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
      97          // Expect one discussion.
      98          $forum1->numdiscussions = 1;
      99          $forum1->cancreatediscussions = true;
     100          $forum1->istracked = true;
     101          $forum1->unreadpostscount = 0;
     102          $forum1->introfiles = [];
     103  
     104          $record = new \stdClass();
     105          $record->course = $course2->id;
     106          $record->userid = $user->id;
     107          $record->forum = $forum2->id;
     108          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     109          $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     110          // Expect two discussions.
     111          $forum2->numdiscussions = 2;
     112          // Default limited role, no create discussion capability enabled.
     113          $forum2->cancreatediscussions = false;
     114          $forum2->istracked = false;
     115  
     116          // Check the forum was correctly created.
     117          $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
     118                  array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
     119  
     120          // Enrol the user in two courses.
     121          // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
     122          $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
     123          // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
     124          $enrol = enrol_get_plugin('manual');
     125          $enrolinstances = enrol_get_instances($course2->id, true);
     126          foreach ($enrolinstances as $courseenrolinstance) {
     127              if ($courseenrolinstance->enrol == "manual") {
     128                  $instance2 = $courseenrolinstance;
     129                  break;
     130              }
     131          }
     132          $enrol->enrol_user($instance2, $user->id);
     133  
     134          // Assign capabilities to view forums for forum 2.
     135          $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
     136          $context2 = \context_module::instance($cm2->id);
     137          $newrole = create_role('Role 2', 'role2', 'Role 2 description');
     138          $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
     139  
     140          // Create what we expect to be returned when querying the two courses.
     141          unset($forum1->displaywordcount);
     142          unset($forum2->displaywordcount);
     143  
     144          $expectedforums = array();
     145          $expectedforums[$forum1->id] = (array) $forum1;
     146          $expectedforums[$forum2->id] = (array) $forum2;
     147  
     148          // Call the external function passing course ids.
     149          $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
     150          $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
     151          $this->assertCount(2, $forums);
     152          foreach ($forums as $forum) {
     153              $this->assertEquals($expectedforums[$forum['id']], $forum);
     154          }
     155  
     156          // Call the external function without passing course id.
     157          $forums = mod_forum_external::get_forums_by_courses();
     158          $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
     159          $this->assertCount(2, $forums);
     160          foreach ($forums as $forum) {
     161              $this->assertEquals($expectedforums[$forum['id']], $forum);
     162          }
     163  
     164          // Unenrol user from second course and alter expected forums.
     165          $enrol->unenrol_user($instance2, $user->id);
     166          unset($expectedforums[$forum2->id]);
     167  
     168          // Call the external function without passing course id.
     169          $forums = mod_forum_external::get_forums_by_courses();
     170          $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
     171          $this->assertCount(1, $forums);
     172          $this->assertEquals($expectedforums[$forum1->id], $forums[0]);
     173          $this->assertTrue($forums[0]['cancreatediscussions']);
     174  
     175          // Change the type of the forum, the user shouldn't be able to add discussions.
     176          $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id));
     177          $forums = mod_forum_external::get_forums_by_courses();
     178          $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
     179          $this->assertFalse($forums[0]['cancreatediscussions']);
     180  
     181          // Call for the second course we unenrolled the user from.
     182          $forums = mod_forum_external::get_forums_by_courses(array($course2->id));
     183          $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
     184          $this->assertCount(0, $forums);
     185      }
     186  
     187      /**
     188       * Test the toggle favourite state
     189       */
     190      public function test_mod_forum_toggle_favourite_state() {
     191          global $USER, $CFG, $DB;
     192  
     193          $this->resetAfterTest(true);
     194  
     195          // Create a user.
     196          $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
     197  
     198          // Set to the user.
     199          self::setUser($user);
     200  
     201          // Create courses to add the modules.
     202          $course1 = self::getDataGenerator()->create_course();
     203          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
     204  
     205          $record = new \stdClass();
     206          $record->introformat = FORMAT_HTML;
     207          $record->course = $course1->id;
     208          $record->trackingtype = FORUM_TRACKING_OFF;
     209          $forum1 = self::getDataGenerator()->create_module('forum', $record);
     210          $forum1->introfiles = [];
     211  
     212          // Add discussions to the forums.
     213          $record = new \stdClass();
     214          $record->course = $course1->id;
     215          $record->userid = $user->id;
     216          $record->forum = $forum1->id;
     217          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     218  
     219          $response = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
     220          $response = \external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
     221          $this->assertTrue($response['userstate']['favourited']);
     222  
     223          $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
     224          $response = \external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
     225          $this->assertFalse($response['userstate']['favourited']);
     226  
     227          $this->setUser(0);
     228          try {
     229              $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
     230          } catch (\moodle_exception $e) {
     231              $this->assertEquals('requireloginerror', $e->errorcode);
     232          }
     233      }
     234  
     235      /**
     236       * Test the toggle pin state
     237       */
     238      public function test_mod_forum_set_pin_state() {
     239          $this->resetAfterTest(true);
     240  
     241          // Create a user.
     242          $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
     243  
     244          // Set to the user.
     245          self::setUser($user);
     246  
     247          // Create courses to add the modules.
     248          $course1 = self::getDataGenerator()->create_course();
     249          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
     250  
     251          $record = new \stdClass();
     252          $record->introformat = FORMAT_HTML;
     253          $record->course = $course1->id;
     254          $record->trackingtype = FORUM_TRACKING_OFF;
     255          $forum1 = self::getDataGenerator()->create_module('forum', $record);
     256          $forum1->introfiles = [];
     257  
     258          // Add discussions to the forums.
     259          $record = new \stdClass();
     260          $record->course = $course1->id;
     261          $record->userid = $user->id;
     262          $record->forum = $forum1->id;
     263          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     264  
     265          try {
     266              $response = mod_forum_external::set_pin_state($discussion1->id, 1);
     267          } catch (\Exception $e) {
     268              $this->assertEquals('cannotpindiscussions', $e->errorcode);
     269          }
     270  
     271          self::setAdminUser();
     272          $response = mod_forum_external::set_pin_state($discussion1->id, 1);
     273          $response = \external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
     274          $this->assertTrue($response['pinned']);
     275  
     276          $response = mod_forum_external::set_pin_state($discussion1->id, 0);
     277          $response = \external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
     278          $this->assertFalse($response['pinned']);
     279      }
     280  
     281      /**
     282       * Test get forum posts
     283       */
     284      public function test_mod_forum_get_forum_discussion_posts() {
     285          global $CFG, $PAGE;
     286  
     287          $this->resetAfterTest(true);
     288  
     289          // Set the CFG variable to allow track forums.
     290          $CFG->forum_trackreadposts = true;
     291  
     292          // Create a user who can track forums.
     293          $record = new \stdClass();
     294          $record->trackforums = true;
     295          $user1 = self::getDataGenerator()->create_user($record);
     296          // Create a bunch of other users to post.
     297          $user2 = self::getDataGenerator()->create_user();
     298          $user3 = self::getDataGenerator()->create_user();
     299  
     300          // Set the first created user to the test user.
     301          self::setUser($user1);
     302  
     303          // Create course to add the module.
     304          $course1 = self::getDataGenerator()->create_course();
     305  
     306          // Forum with tracking off.
     307          $record = new \stdClass();
     308          $record->course = $course1->id;
     309          $record->trackingtype = FORUM_TRACKING_OFF;
     310          $forum1 = self::getDataGenerator()->create_module('forum', $record);
     311          $forum1context = \context_module::instance($forum1->cmid);
     312  
     313          // Forum with tracking enabled.
     314          $record = new \stdClass();
     315          $record->course = $course1->id;
     316          $forum2 = self::getDataGenerator()->create_module('forum', $record);
     317          $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
     318          $forum2context = \context_module::instance($forum2->cmid);
     319  
     320          // Add discussions to the forums.
     321          $record = new \stdClass();
     322          $record->course = $course1->id;
     323          $record->userid = $user1->id;
     324          $record->forum = $forum1->id;
     325          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     326  
     327          $record = new \stdClass();
     328          $record->course = $course1->id;
     329          $record->userid = $user2->id;
     330          $record->forum = $forum1->id;
     331          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     332  
     333          $record = new \stdClass();
     334          $record->course = $course1->id;
     335          $record->userid = $user2->id;
     336          $record->forum = $forum2->id;
     337          $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     338  
     339          // Add 2 replies to the discussion 1 from different users.
     340          $record = new \stdClass();
     341          $record->discussion = $discussion1->id;
     342          $record->parent = $discussion1->firstpost;
     343          $record->userid = $user2->id;
     344          $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
     345          $filename = 'shouldbeanimage.jpg';
     346          // Add a fake inline image to the post.
     347          $filerecordinline = array(
     348              'contextid' => $forum1context->id,
     349              'component' => 'mod_forum',
     350              'filearea'  => 'post',
     351              'itemid'    => $discussion1reply1->id,
     352              'filepath'  => '/',
     353              'filename'  => $filename,
     354          );
     355          $fs = get_file_storage();
     356          $timepost = time();
     357          $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
     358  
     359          $record->parent = $discussion1reply1->id;
     360          $record->userid = $user3->id;
     361          $record->tags = array('Cats', 'Dogs');
     362          $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
     363  
     364          // Enrol the user in the  course.
     365          $enrol = enrol_get_plugin('manual');
     366          // Following line enrol and assign default role id to the user.
     367          // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
     368          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
     369          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
     370  
     371          // Delete one user, to test that we still receive posts by this user.
     372          delete_user($user3);
     373  
     374          // Create what we expect to be returned when querying the discussion.
     375          $expectedposts = array(
     376              'posts' => array(),
     377              'ratinginfo' => array(
     378                  'contextid' => $forum1context->id,
     379                  'component' => 'mod_forum',
     380                  'ratingarea' => 'post',
     381                  'canviewall' => null,
     382                  'canviewany' => null,
     383                  'scales' => array(),
     384                  'ratings' => array(),
     385              ),
     386              'warnings' => array(),
     387          );
     388  
     389          // User pictures are initially empty, we should get the links once the external function is called.
     390          $expectedposts['posts'][] = array(
     391              'id' => $discussion1reply2->id,
     392              'discussion' => $discussion1reply2->discussion,
     393              'parent' => $discussion1reply2->parent,
     394              'userid' => (int) $discussion1reply2->userid,
     395              'created' => $discussion1reply2->created,
     396              'modified' => $discussion1reply2->modified,
     397              'mailed' => $discussion1reply2->mailed,
     398              'subject' => $discussion1reply2->subject,
     399              'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
     400                      $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
     401              'messageformat' => 1,   // This value is usually changed by external_format_text() function.
     402              'messagetrust' => $discussion1reply2->messagetrust,
     403              'attachment' => $discussion1reply2->attachment,
     404              'totalscore' => $discussion1reply2->totalscore,
     405              'mailnow' => $discussion1reply2->mailnow,
     406              'children' => array(),
     407              'canreply' => true,
     408              'postread' => false,
     409              'userfullname' => fullname($user3),
     410              'userpictureurl' => '',
     411              'deleted' => false,
     412              'isprivatereply' => false,
     413              'tags' => \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $discussion1reply2->id),
     414          );
     415          // Cast to expected.
     416          $this->assertCount(2, $expectedposts['posts'][0]['tags']);
     417          $expectedposts['posts'][0]['tags'][0]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][0]['isstandard'];
     418          $expectedposts['posts'][0]['tags'][1]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][1]['isstandard'];
     419  
     420          $expectedposts['posts'][] = array(
     421              'id' => $discussion1reply1->id,
     422              'discussion' => $discussion1reply1->discussion,
     423              'parent' => $discussion1reply1->parent,
     424              'userid' => (int) $discussion1reply1->userid,
     425              'created' => $discussion1reply1->created,
     426              'modified' => $discussion1reply1->modified,
     427              'mailed' => $discussion1reply1->mailed,
     428              'subject' => $discussion1reply1->subject,
     429              'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
     430                      $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
     431              'messageformat' => 1,   // This value is usually changed by external_format_text() function.
     432              'messagetrust' => $discussion1reply1->messagetrust,
     433              'attachment' => $discussion1reply1->attachment,
     434              'messageinlinefiles' => array(
     435                  array(
     436                      'filename' => $filename,
     437                      'filepath' => '/',
     438                      'filesize' => '27',
     439                      'fileurl' => \moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
     440                                      $discussion1reply1->id, '/', $filename),
     441                      'timemodified' => $timepost,
     442                      'mimetype' => 'image/jpeg',
     443                      'isexternalfile' => false,
     444                  )
     445              ),
     446              'totalscore' => $discussion1reply1->totalscore,
     447              'mailnow' => $discussion1reply1->mailnow,
     448              'children' => array($discussion1reply2->id),
     449              'canreply' => true,
     450              'postread' => false,
     451              'userfullname' => fullname($user2),
     452              'userpictureurl' => '',
     453              'deleted' => false,
     454              'isprivatereply' => false,
     455              'tags' => array(),
     456          );
     457  
     458          // Test a discussion with two additional posts (total 3 posts).
     459          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
     460          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     461          $this->assertEquals(3, count($posts['posts']));
     462  
     463          // Generate here the pictures because we need to wait to the external function to init the theme.
     464          $userpicture = new \user_picture($user3);
     465          $userpicture->size = 1; // Size f1.
     466          $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
     467  
     468          $userpicture = new \user_picture($user2);
     469          $userpicture->size = 1; // Size f1.
     470          $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
     471  
     472          // Unset the initial discussion post.
     473          array_pop($posts['posts']);
     474          $this->assertEquals($expectedposts, $posts);
     475  
     476          // Check we receive the unread count correctly on tracked forum.
     477          forum_tp_count_forum_unread_posts($forum2cm, $course1, true);    // Reset static cache.
     478          $result = mod_forum_external::get_forums_by_courses(array($course1->id));
     479          $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
     480          foreach ($result as $f) {
     481              if ($f['id'] == $forum2->id) {
     482                  $this->assertEquals(1, $f['unreadpostscount']);
     483              }
     484          }
     485  
     486          // Test discussion without additional posts. There should be only one post (the one created by the discussion).
     487          $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
     488          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     489          $this->assertEquals(1, count($posts['posts']));
     490  
     491          // Test discussion tracking on not tracked forum.
     492          $result = mod_forum_external::view_forum_discussion($discussion1->id);
     493          $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
     494          $this->assertTrue($result['status']);
     495          $this->assertEmpty($result['warnings']);
     496  
     497          // Test posts have not been marked as read.
     498          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
     499          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     500          foreach ($posts['posts'] as $post) {
     501              $this->assertFalse($post['postread']);
     502          }
     503  
     504          // Test discussion tracking on tracked forum.
     505          $result = mod_forum_external::view_forum_discussion($discussion3->id);
     506          $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
     507          $this->assertTrue($result['status']);
     508          $this->assertEmpty($result['warnings']);
     509  
     510          // Test posts have been marked as read.
     511          $posts = mod_forum_external::get_forum_discussion_posts($discussion3->id, 'modified', 'DESC');
     512          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     513          foreach ($posts['posts'] as $post) {
     514              $this->assertTrue($post['postread']);
     515          }
     516  
     517          // Check we receive 0 unread posts.
     518          forum_tp_count_forum_unread_posts($forum2cm, $course1, true);    // Reset static cache.
     519          $result = mod_forum_external::get_forums_by_courses(array($course1->id));
     520          $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
     521          foreach ($result as $f) {
     522              if ($f['id'] == $forum2->id) {
     523                  $this->assertEquals(0, $f['unreadpostscount']);
     524              }
     525          }
     526      }
     527  
     528      /**
     529       * Test get forum posts
     530       *
     531       * Tests is similar to the get_forum_discussion_posts only utilizing the new return structure and entities
     532       */
     533      public function test_mod_forum_get_discussion_posts() {
     534          global $CFG;
     535  
     536          $this->resetAfterTest(true);
     537  
     538          // Set the CFG variable to allow track forums.
     539          $CFG->forum_trackreadposts = true;
     540  
     541          $urlfactory = \mod_forum\local\container::get_url_factory();
     542          $legacyfactory = \mod_forum\local\container::get_legacy_data_mapper_factory();
     543          $entityfactory = \mod_forum\local\container::get_entity_factory();
     544  
     545          // Create course to add the module.
     546          $course1 = self::getDataGenerator()->create_course();
     547  
     548          // Create a user who can track forums.
     549          $record = new \stdClass();
     550          $record->trackforums = true;
     551          $user1 = self::getDataGenerator()->create_user($record);
     552          // Create a bunch of other users to post.
     553          $user2 = self::getDataGenerator()->create_user();
     554          $user2entity = $entityfactory->get_author_from_stdClass($user2);
     555          $exporteduser2 = [
     556              'id' => (int) $user2->id,
     557              'fullname' => fullname($user2),
     558              'isdeleted' => false,
     559              'groups' => [],
     560              'urls' => [
     561                  'profile' => $urlfactory->get_author_profile_url($user2entity, $course1->id)->out(false),
     562                  'profileimage' => $urlfactory->get_author_profile_image_url($user2entity),
     563              ]
     564          ];
     565          $user2->fullname = $exporteduser2['fullname'];
     566  
     567          $user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]);
     568          $user3entity = $entityfactory->get_author_from_stdClass($user3);
     569          $exporteduser3 = [
     570              'id' => (int) $user3->id,
     571              'fullname' => fullname($user3),
     572              'groups' => [],
     573              'isdeleted' => false,
     574              'urls' => [
     575                  'profile' => $urlfactory->get_author_profile_url($user3entity, $course1->id)->out(false),
     576                  'profileimage' => $urlfactory->get_author_profile_image_url($user3entity),
     577              ]
     578          ];
     579          $user3->fullname = $exporteduser3['fullname'];
     580          $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum');
     581  
     582          // Set the first created user to the test user.
     583          self::setUser($user1);
     584  
     585          // Forum with tracking off.
     586          $record = new \stdClass();
     587          $record->course = $course1->id;
     588          $record->trackingtype = FORUM_TRACKING_OFF;
     589          // Display word count. Otherwise, word and char counts will be set to null by the forum post exporter.
     590          $record->displaywordcount = true;
     591          $forum1 = self::getDataGenerator()->create_module('forum', $record);
     592          $forum1context = \context_module::instance($forum1->cmid);
     593  
     594          // Forum with tracking enabled.
     595          $record = new \stdClass();
     596          $record->course = $course1->id;
     597          $forum2 = self::getDataGenerator()->create_module('forum', $record);
     598          $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
     599          $forum2context = \context_module::instance($forum2->cmid);
     600  
     601          // Add discussions to the forums.
     602          $record = new \stdClass();
     603          $record->course = $course1->id;
     604          $record->userid = $user1->id;
     605          $record->forum = $forum1->id;
     606          $discussion1 = $forumgenerator->create_discussion($record);
     607  
     608          $record = new \stdClass();
     609          $record->course = $course1->id;
     610          $record->userid = $user2->id;
     611          $record->forum = $forum1->id;
     612          $discussion2 = $forumgenerator->create_discussion($record);
     613  
     614          $record = new \stdClass();
     615          $record->course = $course1->id;
     616          $record->userid = $user2->id;
     617          $record->forum = $forum2->id;
     618          $discussion3 = $forumgenerator->create_discussion($record);
     619  
     620          // Add 2 replies to the discussion 1 from different users.
     621          $record = new \stdClass();
     622          $record->discussion = $discussion1->id;
     623          $record->parent = $discussion1->firstpost;
     624          $record->userid = $user2->id;
     625          $discussion1reply1 = $forumgenerator->create_post($record);
     626          $filename = 'shouldbeanimage.jpg';
     627          // Add a fake inline image to the post.
     628          $filerecordinline = array(
     629              'contextid' => $forum1context->id,
     630              'component' => 'mod_forum',
     631              'filearea'  => 'post',
     632              'itemid'    => $discussion1reply1->id,
     633              'filepath'  => '/',
     634              'filename'  => $filename,
     635          );
     636          $fs = get_file_storage();
     637          $timepost = time();
     638          $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
     639  
     640          $record->parent = $discussion1reply1->id;
     641          $record->userid = $user3->id;
     642          $discussion1reply2 = $forumgenerator->create_post($record);
     643  
     644          // Enrol the user in the  course.
     645          $enrol = enrol_get_plugin('manual');
     646          // Following line enrol and assign default role id to the user.
     647          // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
     648          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
     649          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
     650  
     651          // Delete one user, to test that we still receive posts by this user.
     652          delete_user($user3);
     653          $exporteduser3 = [
     654              'id' => (int) $user3->id,
     655              'fullname' => get_string('deleteduser', 'mod_forum'),
     656              'groups' => [],
     657              'isdeleted' => true,
     658              'urls' => [
     659                  'profile' => $urlfactory->get_author_profile_url($user3entity, $course1->id)->out(false),
     660                  'profileimage' => $urlfactory->get_author_profile_image_url($user3entity),
     661              ]
     662          ];
     663  
     664          // Create what we expect to be returned when querying the discussion.
     665          $expectedposts = array(
     666              'posts' => array(),
     667              'courseid' => $course1->id,
     668              'forumid' => $forum1->id,
     669              'ratinginfo' => array(
     670                  'contextid' => $forum1context->id,
     671                  'component' => 'mod_forum',
     672                  'ratingarea' => 'post',
     673                  'canviewall' => null,
     674                  'canviewany' => null,
     675                  'scales' => array(),
     676                  'ratings' => array(),
     677              ),
     678              'warnings' => array(),
     679          );
     680  
     681          // User pictures are initially empty, we should get the links once the external function is called.
     682          $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion);
     683          $isolatedurl->params(['parent' => $discussion1reply2->id]);
     684          $message = file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
     685              $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id);
     686          $expectedposts['posts'][] = array(
     687              'id' => $discussion1reply2->id,
     688              'discussionid' => $discussion1reply2->discussion,
     689              'parentid' => $discussion1reply2->parent,
     690              'hasparent' => true,
     691              'timecreated' => $discussion1reply2->created,
     692              'timemodified' => $discussion1reply2->modified,
     693              'subject' => $discussion1reply2->subject,
     694              'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply2->subject}",
     695              'message' => $message,
     696              'messageformat' => 1,   // This value is usually changed by external_format_text() function.
     697              'unread' => null,
     698              'isdeleted' => false,
     699              'isprivatereply' => false,
     700              'haswordcount' => true,
     701              'wordcount' => count_words($message),
     702              'charcount' => count_letters($message),
     703              'author'=> $exporteduser3,
     704              'attachments' => [],
     705              'tags' => [],
     706              'html' => [
     707                  'rating' => null,
     708                  'taglist' => null,
     709                  'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser3, $discussion1reply2->created)
     710              ],
     711              'capabilities' => [
     712                  'view' => 1,
     713                  'edit' => 0,
     714                  'delete' => 0,
     715                  'split' => 0,
     716                  'reply' => 1,
     717                  'export' => 0,
     718                  'controlreadstatus' => 0,
     719                  'canreplyprivately' => 0,
     720                  'selfenrol' => 0
     721              ],
     722              'urls' => [
     723                  'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->id),
     724                  'viewisolated' => $isolatedurl->out(false),
     725                  'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->parent),
     726                  'edit' => null,
     727                  'delete' =>null,
     728                  'split' => null,
     729                  'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [
     730                      'reply' => $discussion1reply2->id
     731                  ]))->out(false),
     732                  'export' => null,
     733                  'markasread' => null,
     734                  'markasunread' => null,
     735                  'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion),
     736              ],
     737          );
     738  
     739  
     740          $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion);
     741          $isolatedurl->params(['parent' => $discussion1reply1->id]);
     742          $message = file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
     743              $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id);
     744          $expectedposts['posts'][] = array(
     745              'id' => $discussion1reply1->id,
     746              'discussionid' => $discussion1reply1->discussion,
     747              'parentid' => $discussion1reply1->parent,
     748              'hasparent' => true,
     749              'timecreated' => $discussion1reply1->created,
     750              'timemodified' => $discussion1reply1->modified,
     751              'subject' => $discussion1reply1->subject,
     752              'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply1->subject}",
     753              'message' => $message,
     754              'messageformat' => 1,   // This value is usually changed by external_format_text() function.
     755              'unread' => null,
     756              'isdeleted' => false,
     757              'isprivatereply' => false,
     758              'haswordcount' => true,
     759              'wordcount' => count_words($message),
     760              'charcount' => count_letters($message),
     761              'author'=> $exporteduser2,
     762              'attachments' => [],
     763              'tags' => [],
     764              'html' => [
     765                  'rating' => null,
     766                  'taglist' => null,
     767                  'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser2, $discussion1reply1->created)
     768              ],
     769              'capabilities' => [
     770                  'view' => 1,
     771                  'edit' => 0,
     772                  'delete' => 0,
     773                  'split' => 0,
     774                  'reply' => 1,
     775                  'export' => 0,
     776                  'controlreadstatus' => 0,
     777                  'canreplyprivately' => 0,
     778                  'selfenrol' => 0
     779              ],
     780              'urls' => [
     781                  'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->id),
     782                  'viewisolated' => $isolatedurl->out(false),
     783                  'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->parent),
     784                  'edit' => null,
     785                  'delete' =>null,
     786                  'split' => null,
     787                  'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [
     788                      'reply' => $discussion1reply1->id
     789                  ]))->out(false),
     790                  'export' => null,
     791                  'markasread' => null,
     792                  'markasunread' => null,
     793                  'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion),
     794              ],
     795          );
     796  
     797          // Test a discussion with two additional posts (total 3 posts).
     798          $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
     799          $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
     800          $this->assertEquals(3, count($posts['posts']));
     801  
     802          // Unset the initial discussion post.
     803          array_pop($posts['posts']);
     804          $this->assertEquals($expectedposts, $posts);
     805  
     806          // Check we receive the unread count correctly on tracked forum.
     807          forum_tp_count_forum_unread_posts($forum2cm, $course1, true);    // Reset static cache.
     808          $result = mod_forum_external::get_forums_by_courses(array($course1->id));
     809          $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
     810          foreach ($result as $f) {
     811              if ($f['id'] == $forum2->id) {
     812                  $this->assertEquals(1, $f['unreadpostscount']);
     813              }
     814          }
     815  
     816          // Test discussion without additional posts. There should be only one post (the one created by the discussion).
     817          $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC');
     818          $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
     819          $this->assertEquals(1, count($posts['posts']));
     820  
     821          // Test discussion tracking on not tracked forum.
     822          $result = mod_forum_external::view_forum_discussion($discussion1->id);
     823          $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
     824          $this->assertTrue($result['status']);
     825          $this->assertEmpty($result['warnings']);
     826  
     827          // Test posts have not been marked as read.
     828          $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
     829          $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
     830          foreach ($posts['posts'] as $post) {
     831              $this->assertNull($post['unread']);
     832          }
     833  
     834          // Test discussion tracking on tracked forum.
     835          $result = mod_forum_external::view_forum_discussion($discussion3->id);
     836          $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
     837          $this->assertTrue($result['status']);
     838          $this->assertEmpty($result['warnings']);
     839  
     840          // Test posts have been marked as read.
     841          $posts = mod_forum_external::get_discussion_posts($discussion3->id, 'modified', 'DESC');
     842          $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
     843          foreach ($posts['posts'] as $post) {
     844              $this->assertFalse($post['unread']);
     845          }
     846  
     847          // Check we receive 0 unread posts.
     848          forum_tp_count_forum_unread_posts($forum2cm, $course1, true);    // Reset static cache.
     849          $result = mod_forum_external::get_forums_by_courses(array($course1->id));
     850          $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
     851          foreach ($result as $f) {
     852              if ($f['id'] == $forum2->id) {
     853                  $this->assertEquals(0, $f['unreadpostscount']);
     854              }
     855          }
     856      }
     857  
     858      /**
     859       * Test get forum posts
     860       */
     861      public function test_mod_forum_get_forum_discussion_posts_deleted() {
     862          global $CFG, $PAGE;
     863  
     864          $this->resetAfterTest(true);
     865          $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
     866  
     867          // Create a course and enrol some users in it.
     868          $course1 = self::getDataGenerator()->create_course();
     869  
     870          // Create users.
     871          $user1 = self::getDataGenerator()->create_user();
     872          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
     873          $user2 = self::getDataGenerator()->create_user();
     874          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
     875  
     876          // Set the first created user to the test user.
     877          self::setUser($user1);
     878  
     879          // Create test data.
     880          $forum1 = self::getDataGenerator()->create_module('forum', (object) [
     881                  'course' => $course1->id,
     882              ]);
     883          $forum1context = \context_module::instance($forum1->cmid);
     884  
     885          // Add discussions to the forum.
     886          $discussion = $generator->create_discussion((object) [
     887                  'course' => $course1->id,
     888                  'userid' => $user1->id,
     889                  'forum' => $forum1->id,
     890              ]);
     891  
     892          $discussion2 = $generator->create_discussion((object) [
     893                  'course' => $course1->id,
     894                  'userid' => $user2->id,
     895                  'forum' => $forum1->id,
     896              ]);
     897  
     898          // Add replies to the discussion.
     899          $discussionreply1 = $generator->create_post((object) [
     900                  'discussion' => $discussion->id,
     901                  'parent' => $discussion->firstpost,
     902                  'userid' => $user2->id,
     903              ]);
     904          $discussionreply2 = $generator->create_post((object) [
     905                  'discussion' => $discussion->id,
     906                  'parent' => $discussionreply1->id,
     907                  'userid' => $user2->id,
     908                  'subject' => '',
     909                  'message' => '',
     910                  'messageformat' => FORMAT_PLAIN,
     911                  'deleted' => 1,
     912              ]);
     913          $discussionreply3 = $generator->create_post((object) [
     914                  'discussion' => $discussion->id,
     915                  'parent' => $discussion->firstpost,
     916                  'userid' => $user2->id,
     917              ]);
     918  
     919          // Test where some posts have been marked as deleted.
     920          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id, 'modified', 'DESC');
     921          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     922          $deletedsubject = get_string('privacy:request:delete:post:subject', 'mod_forum');
     923          $deletedmessage = get_string('privacy:request:delete:post:message', 'mod_forum');
     924  
     925          foreach ($posts['posts'] as $post) {
     926              if ($post['id'] == $discussionreply2->id) {
     927                  $this->assertTrue($post['deleted']);
     928                  $this->assertEquals($deletedsubject, $post['subject']);
     929                  $this->assertEquals($deletedmessage, $post['message']);
     930              } else {
     931                  $this->assertFalse($post['deleted']);
     932                  $this->assertNotEquals($deletedsubject, $post['subject']);
     933                  $this->assertNotEquals($deletedmessage, $post['message']);
     934              }
     935          }
     936      }
     937  
     938      /**
     939       * Test get forum posts (qanda forum)
     940       */
     941      public function test_mod_forum_get_forum_discussion_posts_qanda() {
     942          global $CFG, $DB;
     943  
     944          $this->resetAfterTest(true);
     945  
     946          $record = new \stdClass();
     947          $user1 = self::getDataGenerator()->create_user($record);
     948          $user2 = self::getDataGenerator()->create_user();
     949  
     950          // Set the first created user to the test user.
     951          self::setUser($user1);
     952  
     953          // Create course to add the module.
     954          $course1 = self::getDataGenerator()->create_course();
     955          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
     956          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
     957  
     958          // Forum with tracking off.
     959          $record = new \stdClass();
     960          $record->course = $course1->id;
     961          $record->type = 'qanda';
     962          $forum1 = self::getDataGenerator()->create_module('forum', $record);
     963          $forum1context = \context_module::instance($forum1->cmid);
     964  
     965          // Add discussions to the forums.
     966          $record = new \stdClass();
     967          $record->course = $course1->id;
     968          $record->userid = $user2->id;
     969          $record->forum = $forum1->id;
     970          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
     971  
     972          // Add 1 reply (not the actual user).
     973          $record = new \stdClass();
     974          $record->discussion = $discussion1->id;
     975          $record->parent = $discussion1->firstpost;
     976          $record->userid = $user2->id;
     977          $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
     978  
     979          // We still see only the original post.
     980          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
     981          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     982          $this->assertEquals(1, count($posts['posts']));
     983  
     984          // Add a new reply, the user is going to be able to see only the original post and their new post.
     985          $record = new \stdClass();
     986          $record->discussion = $discussion1->id;
     987          $record->parent = $discussion1->firstpost;
     988          $record->userid = $user1->id;
     989          $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
     990  
     991          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
     992          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
     993          $this->assertEquals(2, count($posts['posts']));
     994  
     995          // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
     996          $discussion1reply2->created -= $CFG->maxeditingtime * 2;
     997          $DB->update_record('forum_posts', $discussion1reply2);
     998  
     999          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
    1000          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1001          $this->assertEquals(3, count($posts['posts']));
    1002      }
    1003  
    1004      /**
    1005       * Test get forum discussions paginated
    1006       */
    1007      public function test_mod_forum_get_forum_discussions_paginated() {
    1008          global $USER, $CFG, $DB, $PAGE;
    1009  
    1010          $this->resetAfterTest(true);
    1011  
    1012          // Set the CFG variable to allow track forums.
    1013          $CFG->forum_trackreadposts = true;
    1014  
    1015          // Create a user who can track forums.
    1016          $record = new \stdClass();
    1017          $record->trackforums = true;
    1018          $user1 = self::getDataGenerator()->create_user($record);
    1019          // Create a bunch of other users to post.
    1020          $user2 = self::getDataGenerator()->create_user();
    1021          $user3 = self::getDataGenerator()->create_user();
    1022          $user4 = self::getDataGenerator()->create_user();
    1023  
    1024          // Set the first created user to the test user.
    1025          self::setUser($user1);
    1026  
    1027          // Create courses to add the modules.
    1028          $course1 = self::getDataGenerator()->create_course();
    1029  
    1030          // First forum with tracking off.
    1031          $record = new \stdClass();
    1032          $record->course = $course1->id;
    1033          $record->trackingtype = FORUM_TRACKING_OFF;
    1034          $forum1 = self::getDataGenerator()->create_module('forum', $record);
    1035  
    1036          // Add discussions to the forums.
    1037          $record = new \stdClass();
    1038          $record->course = $course1->id;
    1039          $record->userid = $user1->id;
    1040          $record->forum = $forum1->id;
    1041          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1042  
    1043          // Add three replies to the discussion 1 from different users.
    1044          $record = new \stdClass();
    1045          $record->discussion = $discussion1->id;
    1046          $record->parent = $discussion1->firstpost;
    1047          $record->userid = $user2->id;
    1048          $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1049  
    1050          $record->parent = $discussion1reply1->id;
    1051          $record->userid = $user3->id;
    1052          $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1053  
    1054          $record->userid = $user4->id;
    1055          $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1056  
    1057          // Enrol the user in the first course.
    1058          $enrol = enrol_get_plugin('manual');
    1059  
    1060          // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
    1061          $enrolinstances = enrol_get_instances($course1->id, true);
    1062          foreach ($enrolinstances as $courseenrolinstance) {
    1063              if ($courseenrolinstance->enrol == "manual") {
    1064                  $instance1 = $courseenrolinstance;
    1065                  break;
    1066              }
    1067          }
    1068          $enrol->enrol_user($instance1, $user1->id);
    1069  
    1070          // Delete one user.
    1071          delete_user($user4);
    1072  
    1073          // Assign capabilities to view discussions for forum 1.
    1074          $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
    1075          $context = \context_module::instance($cm->id);
    1076          $newrole = create_role('Role 2', 'role2', 'Role 2 description');
    1077          $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
    1078  
    1079          // Create what we expect to be returned when querying the forums.
    1080  
    1081          $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
    1082  
    1083          // User pictures are initially empty, we should get the links once the external function is called.
    1084          $expecteddiscussions = array(
    1085                  'id' => $discussion1->firstpost,
    1086                  'name' => $discussion1->name,
    1087                  'groupid' => (int) $discussion1->groupid,
    1088                  'timemodified' => $discussion1reply3->created,
    1089                  'usermodified' => (int) $discussion1reply3->userid,
    1090                  'timestart' => (int) $discussion1->timestart,
    1091                  'timeend' => (int) $discussion1->timeend,
    1092                  'discussion' => $discussion1->id,
    1093                  'parent' => 0,
    1094                  'userid' => (int) $discussion1->userid,
    1095                  'created' => (int) $post1->created,
    1096                  'modified' => (int) $post1->modified,
    1097                  'mailed' => (int) $post1->mailed,
    1098                  'subject' => $post1->subject,
    1099                  'message' => $post1->message,
    1100                  'messageformat' => (int) $post1->messageformat,
    1101                  'messagetrust' => (int) $post1->messagetrust,
    1102                  'attachment' => $post1->attachment,
    1103                  'totalscore' => (int) $post1->totalscore,
    1104                  'mailnow' => (int) $post1->mailnow,
    1105                  'userfullname' => fullname($user1),
    1106                  'usermodifiedfullname' => fullname($user4),
    1107                  'userpictureurl' => '',
    1108                  'usermodifiedpictureurl' => '',
    1109                  'numreplies' => 3,
    1110                  'numunread' => 0,
    1111                  'pinned' => (bool) FORUM_DISCUSSION_UNPINNED,
    1112                  'locked' => false,
    1113                  'canreply' => false,
    1114                  'canlock' => false
    1115              );
    1116  
    1117          // Call the external function passing forum id.
    1118          $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
    1119          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1120          $expectedreturn = array(
    1121              'discussions' => array($expecteddiscussions),
    1122              'warnings' => array()
    1123          );
    1124  
    1125          // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
    1126          $userpicture = new \user_picture($user1);
    1127          $userpicture->size = 1; // Size f1.
    1128          $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
    1129  
    1130          $userpicture = new \user_picture($user4);
    1131          $userpicture->size = 1; // Size f1.
    1132          $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
    1133  
    1134          $this->assertEquals($expectedreturn, $discussions);
    1135  
    1136          // Call without required view discussion capability.
    1137          $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
    1138          try {
    1139              mod_forum_external::get_forum_discussions_paginated($forum1->id);
    1140              $this->fail('Exception expected due to missing capability.');
    1141          } catch (\moodle_exception $e) {
    1142              $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
    1143          }
    1144  
    1145          // Unenrol user from second course.
    1146          $enrol->unenrol_user($instance1, $user1->id);
    1147  
    1148          // Call for the second course we unenrolled the user from, make sure exception thrown.
    1149          try {
    1150              mod_forum_external::get_forum_discussions_paginated($forum1->id);
    1151              $this->fail('Exception expected due to being unenrolled from the course.');
    1152          } catch (\moodle_exception $e) {
    1153              $this->assertEquals('requireloginerror', $e->errorcode);
    1154          }
    1155  
    1156          $this->setAdminUser();
    1157          $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
    1158          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1159          $this->assertTrue($discussions['discussions'][0]['canlock']);
    1160      }
    1161  
    1162      /**
    1163       * Test get forum discussions paginated (qanda forums)
    1164       */
    1165      public function test_mod_forum_get_forum_discussions_paginated_qanda() {
    1166  
    1167          $this->resetAfterTest(true);
    1168  
    1169          // Create courses to add the modules.
    1170          $course = self::getDataGenerator()->create_course();
    1171  
    1172          $user1 = self::getDataGenerator()->create_user();
    1173          $user2 = self::getDataGenerator()->create_user();
    1174  
    1175          // First forum with tracking off.
    1176          $record = new \stdClass();
    1177          $record->course = $course->id;
    1178          $record->type = 'qanda';
    1179          $forum = self::getDataGenerator()->create_module('forum', $record);
    1180  
    1181          // Add discussions to the forums.
    1182          $discussionrecord = new \stdClass();
    1183          $discussionrecord->course = $course->id;
    1184          $discussionrecord->userid = $user2->id;
    1185          $discussionrecord->forum = $forum->id;
    1186          $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
    1187  
    1188          self::setAdminUser();
    1189          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1190          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1191  
    1192          $this->assertCount(1, $discussions['discussions']);
    1193          $this->assertCount(0, $discussions['warnings']);
    1194  
    1195          self::setUser($user1);
    1196          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
    1197  
    1198          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1199          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1200  
    1201          $this->assertCount(1, $discussions['discussions']);
    1202          $this->assertCount(0, $discussions['warnings']);
    1203  
    1204      }
    1205  
    1206      /**
    1207       * Test get forum discussions
    1208       */
    1209      public function test_mod_forum_get_forum_discussions() {
    1210          global $CFG, $DB, $PAGE;
    1211  
    1212          $this->resetAfterTest(true);
    1213  
    1214          // Set the CFG variable to allow track forums.
    1215          $CFG->forum_trackreadposts = true;
    1216  
    1217          // Create a user who can track forums.
    1218          $record = new \stdClass();
    1219          $record->trackforums = true;
    1220          $user1 = self::getDataGenerator()->create_user($record);
    1221          // Create a bunch of other users to post.
    1222          $user2 = self::getDataGenerator()->create_user();
    1223          $user3 = self::getDataGenerator()->create_user();
    1224          $user4 = self::getDataGenerator()->create_user();
    1225  
    1226          // Set the first created user to the test user.
    1227          self::setUser($user1);
    1228  
    1229          // Create courses to add the modules.
    1230          $course1 = self::getDataGenerator()->create_course();
    1231  
    1232          // First forum with tracking off.
    1233          $record = new \stdClass();
    1234          $record->course = $course1->id;
    1235          $record->trackingtype = FORUM_TRACKING_OFF;
    1236          $forum1 = self::getDataGenerator()->create_module('forum', $record);
    1237  
    1238          // Add discussions to the forums.
    1239          $record = new \stdClass();
    1240          $record->course = $course1->id;
    1241          $record->userid = $user1->id;
    1242          $record->forum = $forum1->id;
    1243          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1244  
    1245          // Add three replies to the discussion 1 from different users.
    1246          $record = new \stdClass();
    1247          $record->discussion = $discussion1->id;
    1248          $record->parent = $discussion1->firstpost;
    1249          $record->userid = $user2->id;
    1250          $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1251  
    1252          $record->parent = $discussion1reply1->id;
    1253          $record->userid = $user3->id;
    1254          $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1255  
    1256          $record->userid = $user4->id;
    1257          $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1258  
    1259          // Enrol the user in the first course.
    1260          $enrol = enrol_get_plugin('manual');
    1261  
    1262          // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
    1263          $enrolinstances = enrol_get_instances($course1->id, true);
    1264          foreach ($enrolinstances as $courseenrolinstance) {
    1265              if ($courseenrolinstance->enrol == "manual") {
    1266                  $instance1 = $courseenrolinstance;
    1267                  break;
    1268              }
    1269          }
    1270          $enrol->enrol_user($instance1, $user1->id);
    1271  
    1272          // Delete one user.
    1273          delete_user($user4);
    1274  
    1275          // Assign capabilities to view discussions for forum 1.
    1276          $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
    1277          $context = \context_module::instance($cm->id);
    1278          $newrole = create_role('Role 2', 'role2', 'Role 2 description');
    1279          $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
    1280  
    1281          // Create what we expect to be returned when querying the forums.
    1282  
    1283          $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
    1284  
    1285          // User pictures are initially empty, we should get the links once the external function is called.
    1286          $expecteddiscussions = array(
    1287              'id' => $discussion1->firstpost,
    1288              'name' => $discussion1->name,
    1289              'groupid' => (int) $discussion1->groupid,
    1290              'timemodified' => (int) $discussion1reply3->created,
    1291              'usermodified' => (int) $discussion1reply3->userid,
    1292              'timestart' => (int) $discussion1->timestart,
    1293              'timeend' => (int) $discussion1->timeend,
    1294              'discussion' => (int) $discussion1->id,
    1295              'parent' => 0,
    1296              'userid' => (int) $discussion1->userid,
    1297              'created' => (int) $post1->created,
    1298              'modified' => (int) $post1->modified,
    1299              'mailed' => (int) $post1->mailed,
    1300              'subject' => $post1->subject,
    1301              'message' => $post1->message,
    1302              'messageformat' => (int) $post1->messageformat,
    1303              'messagetrust' => (int) $post1->messagetrust,
    1304              'attachment' => $post1->attachment,
    1305              'totalscore' => (int) $post1->totalscore,
    1306              'mailnow' => (int) $post1->mailnow,
    1307              'userfullname' => fullname($user1),
    1308              'usermodifiedfullname' => fullname($user4),
    1309              'userpictureurl' => '',
    1310              'usermodifiedpictureurl' => '',
    1311              'numreplies' => 3,
    1312              'numunread' => 0,
    1313              'pinned' => (bool) FORUM_DISCUSSION_UNPINNED,
    1314              'locked' => false,
    1315              'canreply' => false,
    1316              'canlock' => false,
    1317              'starred' => false,
    1318              'canfavourite' => true
    1319          );
    1320  
    1321          // Call the external function passing forum id.
    1322          $discussions = mod_forum_external::get_forum_discussions($forum1->id);
    1323          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1324          $expectedreturn = array(
    1325              'discussions' => array($expecteddiscussions),
    1326              'warnings' => array()
    1327          );
    1328  
    1329          // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
    1330          $userpicture = new \user_picture($user1);
    1331          $userpicture->size = 2; // Size f2.
    1332          $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
    1333  
    1334          $userpicture = new \user_picture($user4);
    1335          $userpicture->size = 2; // Size f2.
    1336          $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
    1337  
    1338          $this->assertEquals($expectedreturn, $discussions);
    1339  
    1340          // Test the starring functionality return.
    1341          $t = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
    1342          $expectedreturn['discussions'][0]['starred'] = true;
    1343          $discussions = mod_forum_external::get_forum_discussions($forum1->id);
    1344          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1345          $this->assertEquals($expectedreturn, $discussions);
    1346  
    1347          // Call without required view discussion capability.
    1348          $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
    1349          try {
    1350              mod_forum_external::get_forum_discussions($forum1->id);
    1351              $this->fail('Exception expected due to missing capability.');
    1352          } catch (\moodle_exception $e) {
    1353              $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
    1354          }
    1355  
    1356          // Unenrol user from second course.
    1357          $enrol->unenrol_user($instance1, $user1->id);
    1358  
    1359          // Call for the second course we unenrolled the user from, make sure exception thrown.
    1360          try {
    1361              mod_forum_external::get_forum_discussions($forum1->id);
    1362              $this->fail('Exception expected due to being unenrolled from the course.');
    1363          } catch (\moodle_exception $e) {
    1364              $this->assertEquals('requireloginerror', $e->errorcode);
    1365          }
    1366  
    1367          $this->setAdminUser();
    1368          $discussions = mod_forum_external::get_forum_discussions($forum1->id);
    1369          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1370          $this->assertTrue($discussions['discussions'][0]['canlock']);
    1371      }
    1372  
    1373      /**
    1374       * Test the sorting in get forum discussions
    1375       */
    1376      public function test_mod_forum_get_forum_discussions_sorting() {
    1377          global $CFG, $DB, $PAGE;
    1378  
    1379          $this->resetAfterTest(true);
    1380  
    1381          // Set the CFG variable to allow track forums.
    1382          $CFG->forum_trackreadposts = true;
    1383  
    1384          // Create a user who can track forums.
    1385          $record = new \stdClass();
    1386          $record->trackforums = true;
    1387          $user1 = self::getDataGenerator()->create_user($record);
    1388          // Create a bunch of other users to post.
    1389          $user2 = self::getDataGenerator()->create_user();
    1390          $user3 = self::getDataGenerator()->create_user();
    1391          $user4 = self::getDataGenerator()->create_user();
    1392  
    1393          // Set the first created user to the test user.
    1394          self::setUser($user1);
    1395  
    1396          // Create courses to add the modules.
    1397          $course1 = self::getDataGenerator()->create_course();
    1398  
    1399          // Enrol the user in the first course.
    1400          $enrol = enrol_get_plugin('manual');
    1401  
    1402          // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
    1403          $enrolinstances = enrol_get_instances($course1->id, true);
    1404          foreach ($enrolinstances as $courseenrolinstance) {
    1405              if ($courseenrolinstance->enrol == "manual") {
    1406                  $instance1 = $courseenrolinstance;
    1407                  break;
    1408              }
    1409          }
    1410          $enrol->enrol_user($instance1, $user1->id);
    1411  
    1412          // First forum with tracking off.
    1413          $record = new \stdClass();
    1414          $record->course = $course1->id;
    1415          $record->trackingtype = FORUM_TRACKING_OFF;
    1416          $forum1 = self::getDataGenerator()->create_module('forum', $record);
    1417  
    1418          // Assign capabilities to view discussions for forum 1.
    1419          $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
    1420          $context = \context_module::instance($cm->id);
    1421          $newrole = create_role('Role 2', 'role2', 'Role 2 description');
    1422          $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
    1423  
    1424          // Add discussions to the forums.
    1425          $record = new \stdClass();
    1426          $record->course = $course1->id;
    1427          $record->userid = $user1->id;
    1428          $record->forum = $forum1->id;
    1429          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1430          sleep(1);
    1431  
    1432          // Add three replies to the discussion 1 from different users.
    1433          $record = new \stdClass();
    1434          $record->discussion = $discussion1->id;
    1435          $record->parent = $discussion1->firstpost;
    1436          $record->userid = $user2->id;
    1437          $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1438          sleep(1);
    1439  
    1440          $record->parent = $discussion1reply1->id;
    1441          $record->userid = $user3->id;
    1442          $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1443          sleep(1);
    1444  
    1445          $record->userid = $user4->id;
    1446          $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    1447          sleep(1);
    1448  
    1449          // Create discussion2.
    1450          $record2 = new \stdClass();
    1451          $record2->course = $course1->id;
    1452          $record2->userid = $user1->id;
    1453          $record2->forum = $forum1->id;
    1454          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record2);
    1455          sleep(1);
    1456  
    1457          // Add one reply to the discussion 2.
    1458          $record2 = new \stdClass();
    1459          $record2->discussion = $discussion2->id;
    1460          $record2->parent = $discussion2->firstpost;
    1461          $record2->userid = $user2->id;
    1462          $discussion2reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record2);
    1463          sleep(1);
    1464  
    1465          // Create discussion 3.
    1466          $record3 = new \stdClass();
    1467          $record3->course = $course1->id;
    1468          $record3->userid = $user1->id;
    1469          $record3->forum = $forum1->id;
    1470          $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record3);
    1471          sleep(1);
    1472  
    1473          // Add two replies to the discussion 3.
    1474          $record3 = new \stdClass();
    1475          $record3->discussion = $discussion3->id;
    1476          $record3->parent = $discussion3->firstpost;
    1477          $record3->userid = $user2->id;
    1478          $discussion3reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3);
    1479          sleep(1);
    1480  
    1481          $record3->parent = $discussion3reply1->id;
    1482          $record3->userid = $user3->id;
    1483          $discussion3reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3);
    1484  
    1485          // Call the external function passing forum id.
    1486          $discussions = mod_forum_external::get_forum_discussions($forum1->id);
    1487          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1488          // Discussions should be ordered by last post date in descending order by default.
    1489          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id);
    1490          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
    1491          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
    1492  
    1493          $vaultfactory = \mod_forum\local\container::get_vault_factory();
    1494          $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
    1495  
    1496          // Call the external function passing forum id and sort order parameter.
    1497          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC);
    1498          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1499          // Discussions should be ordered by last post date in ascending order.
    1500          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
    1501          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
    1502          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
    1503  
    1504          // Call the external function passing forum id and sort order parameter.
    1505          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_DESC);
    1506          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1507          // Discussions should be ordered by discussion creation date in descending order.
    1508          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id);
    1509          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
    1510          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
    1511  
    1512          // Call the external function passing forum id and sort order parameter.
    1513          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_ASC);
    1514          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1515          // Discussions should be ordered by discussion creation date in ascending order.
    1516          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
    1517          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
    1518          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
    1519  
    1520          // Call the external function passing forum id and sort order parameter.
    1521          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_DESC);
    1522          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1523          // Discussions should be ordered by the number of replies in descending order.
    1524          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
    1525          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
    1526          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion2->id);
    1527  
    1528          // Call the external function passing forum id and sort order parameter.
    1529          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_ASC);
    1530          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1531          // Discussions should be ordered by the number of replies in ascending order.
    1532          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
    1533          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
    1534          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
    1535  
    1536          // Pin discussion2.
    1537          $DB->update_record('forum_discussions',
    1538              (object) array('id' => $discussion2->id, 'pinned' => FORUM_DISCUSSION_PINNED));
    1539  
    1540          // Call the external function passing forum id.
    1541          $discussions = mod_forum_external::get_forum_discussions($forum1->id);
    1542          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1543          // Discussions should be ordered by last post date in descending order by default.
    1544          // Pinned discussions should be at the top of the list.
    1545          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
    1546          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
    1547          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
    1548  
    1549          // Call the external function passing forum id and sort order parameter.
    1550          $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC);
    1551          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    1552          // Discussions should be ordered by last post date in ascending order.
    1553          // Pinned discussions should be at the top of the list.
    1554          $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
    1555          $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion1->id);
    1556          $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
    1557      }
    1558  
    1559      /**
    1560       * Test add_discussion_post
    1561       */
    1562      public function test_add_discussion_post() {
    1563          global $CFG;
    1564  
    1565          $this->resetAfterTest(true);
    1566  
    1567          $user = self::getDataGenerator()->create_user();
    1568          $otheruser = self::getDataGenerator()->create_user();
    1569  
    1570          self::setAdminUser();
    1571  
    1572          // Create course to add the module.
    1573          $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
    1574  
    1575          // Forum with tracking off.
    1576          $record = new \stdClass();
    1577          $record->course = $course->id;
    1578          $forum = self::getDataGenerator()->create_module('forum', $record);
    1579          $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
    1580          $forumcontext = \context_module::instance($forum->cmid);
    1581  
    1582          // Add discussions to the forums.
    1583          $record = new \stdClass();
    1584          $record->course = $course->id;
    1585          $record->userid = $user->id;
    1586          $record->forum = $forum->id;
    1587          $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1588  
    1589          // Try to post (user not enrolled).
    1590          self::setUser($user);
    1591          try {
    1592              mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
    1593              $this->fail('Exception expected due to being unenrolled from the course.');
    1594          } catch (\moodle_exception $e) {
    1595              $this->assertEquals('requireloginerror', $e->errorcode);
    1596          }
    1597  
    1598          $this->getDataGenerator()->enrol_user($user->id, $course->id);
    1599          $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
    1600  
    1601          $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
    1602          $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
    1603  
    1604          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    1605          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1606          // We receive the discussion and the post.
    1607          $this->assertEquals(2, count($posts['posts']));
    1608  
    1609          $tested = false;
    1610          foreach ($posts['posts'] as $thispost) {
    1611              if ($createdpost['postid'] == $thispost['id']) {
    1612                  $this->assertEquals('some subject', $thispost['subject']);
    1613                  $this->assertEquals('some text here...', $thispost['message']);
    1614                  $this->assertEquals(FORMAT_HTML, $thispost['messageformat']); // This is the default if format was not specified.
    1615                  $tested = true;
    1616              }
    1617          }
    1618          $this->assertTrue($tested);
    1619  
    1620          // Let's simulate a call with any other format, it should be stored that way.
    1621          global $DB; // Yes, we are going to use DB facilities too, because cannot rely on other functions for checking
    1622                      // the format. They eat it completely (going back to FORMAT_HTML. So we only can trust DB for further
    1623                      // processing.
    1624          $formats = [FORMAT_PLAIN, FORMAT_MOODLE, FORMAT_MARKDOWN, FORMAT_HTML];
    1625          $options = [];
    1626          foreach ($formats as $format) {
    1627              $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost,
    1628                  'with some format', 'some formatted here...', $options, $format);
    1629              $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
    1630              $dbformat = $DB->get_field('forum_posts', 'messageformat', ['id' => $createdpost['postid']]);
    1631              $this->assertEquals($format, $dbformat);
    1632          }
    1633  
    1634          // Now let's try the 'topreferredformat' option. That should end with the content
    1635          // transformed and the format being FORMAT_HTML (when, like in this case,  user preferred
    1636          // format is HTML, inferred from editor in preferences).
    1637          $options = [['name' => 'topreferredformat', 'value' => true]];
    1638          $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost,
    1639              'interesting subject', 'with some https://example.com link', $options, FORMAT_MOODLE);
    1640          $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
    1641          $dbpost = $DB->get_record('forum_posts', ['id' => $createdpost['postid']]);
    1642          // Format HTML and content converted, we should get.
    1643          $this->assertEquals(FORMAT_HTML, $dbpost->messageformat);
    1644          $this->assertEquals('<div class="text_to_html">with some https://example.com link</div>', $dbpost->message);
    1645  
    1646          // Test inline and regular attachment in post
    1647          // Create a file in a draft area for inline attachments.
    1648          $draftidinlineattach = file_get_unused_draft_itemid();
    1649          $draftidattach = file_get_unused_draft_itemid();
    1650          self::setUser($user);
    1651          $usercontext = \context_user::instance($user->id);
    1652          $filepath = '/';
    1653          $filearea = 'draft';
    1654          $component = 'user';
    1655          $filenameimg = 'shouldbeanimage.txt';
    1656          $filerecordinline = array(
    1657              'contextid' => $usercontext->id,
    1658              'component' => $component,
    1659              'filearea'  => $filearea,
    1660              'itemid'    => $draftidinlineattach,
    1661              'filepath'  => $filepath,
    1662              'filename'  => $filenameimg,
    1663          );
    1664          $fs = get_file_storage();
    1665  
    1666          // Create a file in a draft area for regular attachments.
    1667          $filerecordattach = $filerecordinline;
    1668          $attachfilename = 'attachment.txt';
    1669          $filerecordattach['filename'] = $attachfilename;
    1670          $filerecordattach['itemid'] = $draftidattach;
    1671          $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
    1672          $fs->create_file_from_string($filerecordattach, 'simple text attachment');
    1673  
    1674          $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
    1675                           array('name' => 'attachmentsid', 'value' => $draftidattach));
    1676          $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
    1677                       . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
    1678                       . '" alt="inlineimage">.';
    1679          $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
    1680                                                                 $dummytext, $options);
    1681          $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
    1682  
    1683          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    1684          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1685          // We receive the discussion and the post.
    1686          // Can't guarantee order of posts during tests.
    1687          $postfound = false;
    1688          foreach ($posts['posts'] as $thispost) {
    1689              if ($createdpost['postid'] == $thispost['id']) {
    1690                  $this->assertEquals($createdpost['postid'], $thispost['id']);
    1691                  $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
    1692                  $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
    1693                  $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
    1694                  $this->assertStringContainsString('pluginfile.php', $thispost['message']);
    1695                  $postfound = true;
    1696                  break;
    1697              }
    1698          }
    1699  
    1700          $this->assertTrue($postfound);
    1701  
    1702          // Check not posting in groups the user is not member of.
    1703          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
    1704          groups_add_member($group->id, $otheruser->id);
    1705  
    1706          $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
    1707          $record->forum = $forum->id;
    1708          $record->userid = $otheruser->id;
    1709          $record->groupid = $group->id;
    1710          $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1711  
    1712          try {
    1713              mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
    1714              $this->fail('Exception expected due to invalid permissions for posting.');
    1715          } catch (\moodle_exception $e) {
    1716              $this->assertEquals('nopostforum', $e->errorcode);
    1717          }
    1718      }
    1719  
    1720      /**
    1721       * Test add_discussion_post and auto subscription to a discussion.
    1722       */
    1723      public function test_add_discussion_post_subscribe_discussion() {
    1724          global $USER;
    1725  
    1726          $this->resetAfterTest(true);
    1727  
    1728          self::setAdminUser();
    1729  
    1730          $user = self::getDataGenerator()->create_user();
    1731          $admin = get_admin();
    1732          // Create course to add the module.
    1733          $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
    1734  
    1735          $this->getDataGenerator()->enrol_user($user->id, $course->id);
    1736  
    1737          // Forum with tracking off.
    1738          $record = new \stdClass();
    1739          $record->course = $course->id;
    1740          $forum = self::getDataGenerator()->create_module('forum', $record);
    1741          $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
    1742  
    1743          // Add discussions to the forums.
    1744          $record = new \stdClass();
    1745          $record->course = $course->id;
    1746          $record->userid = $admin->id;
    1747          $record->forum = $forum->id;
    1748          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1749          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    1750  
    1751          // Try to post as user.
    1752          self::setUser($user);
    1753          // Enable auto subscribe discussion.
    1754          $USER->autosubscribe = true;
    1755          // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference enabled).
    1756          mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject', 'some text here...');
    1757  
    1758          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
    1759          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1760          // We receive the discussion and the post.
    1761          $this->assertEquals(2, count($posts['posts']));
    1762          // The user should be subscribed to the discussion after adding a discussion post.
    1763          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
    1764  
    1765          // Disable auto subscribe discussion.
    1766          $USER->autosubscribe = false;
    1767          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
    1768          // Add a discussion post in a forum discussion where the user is subscribed (auto-subscribe preference disabled).
    1769          mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject 1', 'some text here 1...');
    1770  
    1771          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
    1772          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1773          // We receive the discussion and the post.
    1774          $this->assertEquals(3, count($posts['posts']));
    1775          // The user should still be subscribed to the discussion after adding a discussion post.
    1776          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
    1777  
    1778          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
    1779          // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled).
    1780          mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...');
    1781  
    1782          $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
    1783          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1784          // We receive the discussion and the post.
    1785          $this->assertEquals(2, count($posts['posts']));
    1786          // The user should still not be subscribed to the discussion after adding a discussion post.
    1787          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
    1788  
    1789          // Passing a value for the discussionsubscribe option parameter.
    1790          $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
    1791          // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled),
    1792          // and the option parameter 'discussionsubscribe' => true in the webservice.
    1793          $option = array('name' => 'discussionsubscribe', 'value' => true);
    1794          $options[] = $option;
    1795          mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...',
    1796              $options);
    1797  
    1798          $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
    1799          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    1800          // We receive the discussion and the post.
    1801          $this->assertEquals(3, count($posts['posts']));
    1802          // The user should now be subscribed to the discussion after adding a discussion post.
    1803          $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
    1804      }
    1805  
    1806      /*
    1807       * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
    1808       */
    1809      public function test_add_discussion() {
    1810          global $CFG, $USER;
    1811          $this->resetAfterTest(true);
    1812  
    1813          // Create courses to add the modules.
    1814          $course = self::getDataGenerator()->create_course();
    1815  
    1816          $user1 = self::getDataGenerator()->create_user();
    1817          $user2 = self::getDataGenerator()->create_user();
    1818  
    1819          // First forum with tracking off.
    1820          $record = new \stdClass();
    1821          $record->course = $course->id;
    1822          $record->type = 'news';
    1823          $forum = self::getDataGenerator()->create_module('forum', $record);
    1824  
    1825          self::setUser($user1);
    1826          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
    1827  
    1828          try {
    1829              mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
    1830              $this->fail('Exception expected due to invalid permissions.');
    1831          } catch (\moodle_exception $e) {
    1832              $this->assertEquals('cannotcreatediscussion', $e->errorcode);
    1833          }
    1834  
    1835          self::setAdminUser();
    1836          $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
    1837          $createddiscussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
    1838  
    1839          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1840          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1841  
    1842          $this->assertCount(1, $discussions['discussions']);
    1843          $this->assertCount(0, $discussions['warnings']);
    1844  
    1845          $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
    1846          $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
    1847          $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
    1848          $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
    1849  
    1850          $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
    1851                                                                  array('options' => array('name' => 'discussionpinned',
    1852                                                                                           'value' => true)));
    1853          $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
    1854          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1855          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1856          $this->assertCount(3, $discussions['discussions']);
    1857          $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
    1858  
    1859          // Test inline and regular attachment in new discussion
    1860          // Create a file in a draft area for inline attachments.
    1861  
    1862          $fs = get_file_storage();
    1863  
    1864          $draftidinlineattach = file_get_unused_draft_itemid();
    1865          $draftidattach = file_get_unused_draft_itemid();
    1866  
    1867          $usercontext = \context_user::instance($USER->id);
    1868          $filepath = '/';
    1869          $filearea = 'draft';
    1870          $component = 'user';
    1871          $filenameimg = 'shouldbeanimage.txt';
    1872          $filerecord = array(
    1873              'contextid' => $usercontext->id,
    1874              'component' => $component,
    1875              'filearea'  => $filearea,
    1876              'itemid'    => $draftidinlineattach,
    1877              'filepath'  => $filepath,
    1878              'filename'  => $filenameimg,
    1879          );
    1880  
    1881          // Create a file in a draft area for regular attachments.
    1882          $filerecordattach = $filerecord;
    1883          $attachfilename = 'attachment.txt';
    1884          $filerecordattach['filename'] = $attachfilename;
    1885          $filerecordattach['itemid'] = $draftidattach;
    1886          $fs->create_file_from_string($filerecord, 'image contents (not really)');
    1887          $fs->create_file_from_string($filerecordattach, 'simple text attachment');
    1888  
    1889          $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
    1890                      "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
    1891                      '" alt="inlineimage">.';
    1892  
    1893          $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
    1894                           array('name' => 'attachmentsid', 'value' => $draftidattach));
    1895          $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
    1896                                                                  $dummytext, -1, $options);
    1897          $createddiscussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
    1898  
    1899          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1900          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1901  
    1902          $this->assertCount(4, $discussions['discussions']);
    1903          $this->assertCount(0, $createddiscussion['warnings']);
    1904          // Can't guarantee order of posts during tests.
    1905          $postfound = false;
    1906          foreach ($discussions['discussions'] as $thisdiscussion) {
    1907              if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
    1908                  $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
    1909                  $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
    1910                  $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
    1911                  $this->assertStringNotContainsString('draftfile.php', $thisdiscussion['message']);
    1912                  $this->assertStringContainsString('pluginfile.php', $thisdiscussion['message']);
    1913                  $postfound = true;
    1914                  break;
    1915              }
    1916          }
    1917  
    1918          $this->assertTrue($postfound);
    1919      }
    1920  
    1921      /**
    1922       * Test adding discussions in a course with gorups
    1923       */
    1924      public function test_add_discussion_in_course_with_groups() {
    1925          global $CFG;
    1926  
    1927          $this->resetAfterTest(true);
    1928  
    1929          // Create course to add the module.
    1930          $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
    1931          $user = self::getDataGenerator()->create_user();
    1932          $this->getDataGenerator()->enrol_user($user->id, $course->id);
    1933  
    1934          // Forum forcing separate gropus.
    1935          $record = new \stdClass();
    1936          $record->course = $course->id;
    1937          $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
    1938  
    1939          // Try to post (user not enrolled).
    1940          self::setUser($user);
    1941  
    1942          // The user is not enroled in any group, try to post in a forum with separate groups.
    1943          try {
    1944              mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
    1945              $this->fail('Exception expected due to invalid group permissions.');
    1946          } catch (\moodle_exception $e) {
    1947              $this->assertEquals('cannotcreatediscussion', $e->errorcode);
    1948          }
    1949  
    1950          try {
    1951              mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
    1952              $this->fail('Exception expected due to invalid group permissions.');
    1953          } catch (\moodle_exception $e) {
    1954              $this->assertEquals('cannotcreatediscussion', $e->errorcode);
    1955          }
    1956  
    1957          // Create a group.
    1958          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
    1959  
    1960          // Try to post in a group the user is not enrolled.
    1961          try {
    1962              mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
    1963              $this->fail('Exception expected due to invalid group permissions.');
    1964          } catch (\moodle_exception $e) {
    1965              $this->assertEquals('cannotcreatediscussion', $e->errorcode);
    1966          }
    1967  
    1968          // Add the user to a group.
    1969          groups_add_member($group->id, $user->id);
    1970  
    1971          // Try to post in a group the user is not enrolled.
    1972          try {
    1973              mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
    1974              $this->fail('Exception expected due to invalid group.');
    1975          } catch (\moodle_exception $e) {
    1976              $this->assertEquals('cannotcreatediscussion', $e->errorcode);
    1977          }
    1978  
    1979          // Nost add the discussion using a valid group.
    1980          $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
    1981          $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
    1982  
    1983          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1984          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1985  
    1986          $this->assertCount(1, $discussions['discussions']);
    1987          $this->assertCount(0, $discussions['warnings']);
    1988          $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
    1989          $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
    1990  
    1991          // Now add a discussions without indicating a group. The function should guess the correct group.
    1992          $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
    1993          $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
    1994  
    1995          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    1996          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    1997  
    1998          $this->assertCount(2, $discussions['discussions']);
    1999          $this->assertCount(0, $discussions['warnings']);
    2000          $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
    2001          $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
    2002  
    2003          // Enrol the same user in other group.
    2004          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
    2005          groups_add_member($group2->id, $user->id);
    2006  
    2007          // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
    2008          $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
    2009          $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
    2010  
    2011          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    2012          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    2013  
    2014          $this->assertCount(3, $discussions['discussions']);
    2015          $this->assertCount(0, $discussions['warnings']);
    2016          $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
    2017          $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
    2018          $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
    2019  
    2020      }
    2021  
    2022      /*
    2023       * Test set_lock_state.
    2024       */
    2025      public function test_set_lock_state() {
    2026          global $DB;
    2027          $this->resetAfterTest(true);
    2028  
    2029          // Create courses to add the modules.
    2030          $course = self::getDataGenerator()->create_course();
    2031          $user = self::getDataGenerator()->create_user();
    2032          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
    2033  
    2034          // First forum with tracking off.
    2035          $record = new \stdClass();
    2036          $record->course = $course->id;
    2037          $record->type = 'news';
    2038          $forum = self::getDataGenerator()->create_module('forum', $record);
    2039  
    2040          $record = new \stdClass();
    2041          $record->course = $course->id;
    2042          $record->userid = $user->id;
    2043          $record->forum = $forum->id;
    2044          $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    2045  
    2046          // User who is a student.
    2047          self::setUser($user);
    2048          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual');
    2049  
    2050          // Only a teacher should be able to lock a discussion.
    2051          try {
    2052              $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
    2053              $this->fail('Exception expected due to missing capability.');
    2054          } catch (\moodle_exception $e) {
    2055              $this->assertEquals('errorcannotlock', $e->errorcode);
    2056          }
    2057  
    2058          // Set the lock.
    2059          self::setAdminUser();
    2060          $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
    2061          $result = \external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
    2062          $this->assertTrue($result['locked']);
    2063          $this->assertNotEquals(0, $result['times']['locked']);
    2064  
    2065          // Unset the lock.
    2066          $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, time());
    2067          $result = \external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
    2068          $this->assertFalse($result['locked']);
    2069          $this->assertEquals('0', $result['times']['locked']);
    2070      }
    2071  
    2072      /*
    2073       * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
    2074       */
    2075      public function test_can_add_discussion() {
    2076          global $DB;
    2077          $this->resetAfterTest(true);
    2078  
    2079          // Create courses to add the modules.
    2080          $course = self::getDataGenerator()->create_course();
    2081  
    2082          $user = self::getDataGenerator()->create_user();
    2083  
    2084          // First forum with tracking off.
    2085          $record = new \stdClass();
    2086          $record->course = $course->id;
    2087          $record->type = 'news';
    2088          $forum = self::getDataGenerator()->create_module('forum', $record);
    2089  
    2090          // User with no permissions to add in a news forum.
    2091          self::setUser($user);
    2092          $this->getDataGenerator()->enrol_user($user->id, $course->id);
    2093  
    2094          $result = mod_forum_external::can_add_discussion($forum->id);
    2095          $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
    2096          $this->assertFalse($result['status']);
    2097          $this->assertFalse($result['canpindiscussions']);
    2098          $this->assertTrue($result['cancreateattachment']);
    2099  
    2100          // Disable attachments.
    2101          $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
    2102          $result = mod_forum_external::can_add_discussion($forum->id);
    2103          $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
    2104          $this->assertFalse($result['status']);
    2105          $this->assertFalse($result['canpindiscussions']);
    2106          $this->assertFalse($result['cancreateattachment']);
    2107          $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id));    // Enable attachments again.
    2108  
    2109          self::setAdminUser();
    2110          $result = mod_forum_external::can_add_discussion($forum->id);
    2111          $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
    2112          $this->assertTrue($result['status']);
    2113          $this->assertTrue($result['canpindiscussions']);
    2114          $this->assertTrue($result['cancreateattachment']);
    2115      }
    2116  
    2117      /*
    2118       * A basic test to make sure users cannot post to forum after the cutoff date.
    2119       */
    2120      public function test_can_add_discussion_after_cutoff() {
    2121          $this->resetAfterTest(true);
    2122  
    2123          // Create courses to add the modules.
    2124          $course = self::getDataGenerator()->create_course();
    2125  
    2126          $user = self::getDataGenerator()->create_user();
    2127  
    2128          // Create a forum with cutoff date set to a past date.
    2129          $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]);
    2130  
    2131          // User with no mod/forum:canoverridecutoff capability.
    2132          self::setUser($user);
    2133          $this->getDataGenerator()->enrol_user($user->id, $course->id);
    2134  
    2135          $result = mod_forum_external::can_add_discussion($forum->id);
    2136          $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
    2137          $this->assertFalse($result['status']);
    2138  
    2139          self::setAdminUser();
    2140          $result = mod_forum_external::can_add_discussion($forum->id);
    2141          $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
    2142          $this->assertTrue($result['status']);
    2143      }
    2144  
    2145      /**
    2146       * Test get forum posts discussions including rating information.
    2147       */
    2148      public function test_mod_forum_get_forum_discussion_rating_information() {
    2149          global $DB, $CFG;
    2150          require_once($CFG->dirroot . '/rating/lib.php');
    2151  
    2152          $this->resetAfterTest(true);
    2153  
    2154          $user1 = self::getDataGenerator()->create_user();
    2155          $user2 = self::getDataGenerator()->create_user();
    2156          $user3 = self::getDataGenerator()->create_user();
    2157          $teacher = self::getDataGenerator()->create_user();
    2158  
    2159          // Create course to add the module.
    2160          $course = self::getDataGenerator()->create_course();
    2161  
    2162          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
    2163          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
    2164          $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual');
    2165          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual');
    2166          $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual');
    2167          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
    2168  
    2169          // Create the forum.
    2170          $record = new \stdClass();
    2171          $record->course = $course->id;
    2172          // Set Aggregate type = Average of ratings.
    2173          $record->assessed = RATING_AGGREGATE_AVERAGE;
    2174          $record->scale = 100;
    2175          $forum = self::getDataGenerator()->create_module('forum', $record);
    2176          $context = \context_module::instance($forum->cmid);
    2177  
    2178          // Add discussion to the forum.
    2179          $record = new \stdClass();
    2180          $record->course = $course->id;
    2181          $record->userid = $user1->id;
    2182          $record->forum = $forum->id;
    2183          $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    2184  
    2185          // Retrieve the first post.
    2186          $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
    2187  
    2188          // Rate the discussion as user2.
    2189          $rating1 = new \stdClass();
    2190          $rating1->contextid = $context->id;
    2191          $rating1->component = 'mod_forum';
    2192          $rating1->ratingarea = 'post';
    2193          $rating1->itemid = $post->id;
    2194          $rating1->rating = 50;
    2195          $rating1->scaleid = 100;
    2196          $rating1->userid = $user2->id;
    2197          $rating1->timecreated = time();
    2198          $rating1->timemodified = time();
    2199          $rating1->id = $DB->insert_record('rating', $rating1);
    2200  
    2201          // Rate the discussion as user3.
    2202          $rating2 = new \stdClass();
    2203          $rating2->contextid = $context->id;
    2204          $rating2->component = 'mod_forum';
    2205          $rating2->ratingarea = 'post';
    2206          $rating2->itemid = $post->id;
    2207          $rating2->rating = 100;
    2208          $rating2->scaleid = 100;
    2209          $rating2->userid = $user3->id;
    2210          $rating2->timecreated = time() + 1;
    2211          $rating2->timemodified = time() + 1;
    2212          $rating2->id = $DB->insert_record('rating', $rating2);
    2213  
    2214          // Retrieve the rating for the post as student.
    2215          $this->setUser($user1);
    2216          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2217          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2218          $this->assertCount(1, $posts['ratinginfo']['ratings']);
    2219          $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
    2220          $this->assertFalse($posts['ratinginfo']['canviewall']);
    2221          $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']);
    2222          $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
    2223          $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
    2224  
    2225          // Retrieve the rating for the post as teacher.
    2226          $this->setUser($teacher);
    2227          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2228          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2229          $this->assertCount(1, $posts['ratinginfo']['ratings']);
    2230          $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
    2231          $this->assertTrue($posts['ratinginfo']['canviewall']);
    2232          $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']);
    2233          $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
    2234          $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
    2235      }
    2236  
    2237      /**
    2238       * Test mod_forum_get_forum_access_information.
    2239       */
    2240      public function test_mod_forum_get_forum_access_information() {
    2241          global $DB;
    2242  
    2243          $this->resetAfterTest(true);
    2244  
    2245          $student = self::getDataGenerator()->create_user();
    2246          $course = self::getDataGenerator()->create_course();
    2247          // Create the forum.
    2248          $record = new \stdClass();
    2249          $record->course = $course->id;
    2250          $forum = self::getDataGenerator()->create_module('forum', $record);
    2251  
    2252          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
    2253          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
    2254  
    2255          self::setUser($student);
    2256          $result = mod_forum_external::get_forum_access_information($forum->id);
    2257          $result = \external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
    2258  
    2259          // Check default values for capabilities.
    2260          $enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment',
    2261              'canexportownpost', 'cancantogglefavourite', 'candeleteownpost', 'canallowforcesubscribe');
    2262  
    2263          unset($result['warnings']);
    2264          foreach ($result as $capname => $capvalue) {
    2265              if (in_array($capname, $enabledcaps)) {
    2266                  $this->assertTrue($capvalue);
    2267              } else {
    2268                  $this->assertFalse($capvalue);
    2269              }
    2270          }
    2271          // Now, unassign some capabilities.
    2272          unassign_capability('mod/forum:deleteownpost', $studentrole->id);
    2273          unassign_capability('mod/forum:allowforcesubscribe', $studentrole->id);
    2274          array_pop($enabledcaps);
    2275          array_pop($enabledcaps);
    2276          accesslib_clear_all_caches_for_unit_testing();
    2277  
    2278          $result = mod_forum_external::get_forum_access_information($forum->id);
    2279          $result = \external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
    2280          unset($result['warnings']);
    2281          foreach ($result as $capname => $capvalue) {
    2282              if (in_array($capname, $enabledcaps)) {
    2283                  $this->assertTrue($capvalue);
    2284              } else {
    2285                  $this->assertFalse($capvalue);
    2286              }
    2287          }
    2288      }
    2289  
    2290      /**
    2291       * Test add_discussion_post
    2292       */
    2293      public function test_add_discussion_post_private() {
    2294          global $DB;
    2295  
    2296          $this->resetAfterTest(true);
    2297  
    2298          self::setAdminUser();
    2299  
    2300          // Create course to add the module.
    2301          $course = self::getDataGenerator()->create_course();
    2302  
    2303          // Standard forum.
    2304          $record = new \stdClass();
    2305          $record->course = $course->id;
    2306          $forum = self::getDataGenerator()->create_module('forum', $record);
    2307          $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
    2308          $forumcontext = \context_module::instance($forum->cmid);
    2309          $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
    2310  
    2311          // Create an enrol users.
    2312          $student1 = self::getDataGenerator()->create_user();
    2313          $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
    2314          $student2 = self::getDataGenerator()->create_user();
    2315          $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
    2316          $teacher1 = self::getDataGenerator()->create_user();
    2317          $this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher');
    2318          $teacher2 = self::getDataGenerator()->create_user();
    2319          $this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher');
    2320  
    2321          // Add a new discussion to the forum.
    2322          self::setUser($student1);
    2323          $record = new \stdClass();
    2324          $record->course = $course->id;
    2325          $record->userid = $student1->id;
    2326          $record->forum = $forum->id;
    2327          $discussion = $generator->create_discussion($record);
    2328  
    2329          // Have the teacher reply privately.
    2330          self::setUser($teacher1);
    2331          $post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [
    2332                  [
    2333                      'name' => 'private',
    2334                      'value' => true,
    2335                  ],
    2336              ]);
    2337          $post = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post);
    2338          $privatereply = $DB->get_record('forum_posts', array('id' => $post['postid']));
    2339          $this->assertEquals($student1->id, $privatereply->privatereplyto);
    2340          // Bump the time of the private reply to ensure order.
    2341          $privatereply->created++;
    2342          $privatereply->modified = $privatereply->created;
    2343          $DB->update_record('forum_posts', $privatereply);
    2344  
    2345          // The teacher will receive their private reply.
    2346          self::setUser($teacher1);
    2347          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2348          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2349          $this->assertEquals(2, count($posts['posts']));
    2350          $this->assertTrue($posts['posts'][0]['isprivatereply']);
    2351  
    2352          // Another teacher on the course will also receive the private reply.
    2353          self::setUser($teacher2);
    2354          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2355          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2356          $this->assertEquals(2, count($posts['posts']));
    2357          $this->assertTrue($posts['posts'][0]['isprivatereply']);
    2358  
    2359          // The student will receive the private reply.
    2360          self::setUser($student1);
    2361          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2362          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2363          $this->assertEquals(2, count($posts['posts']));
    2364          $this->assertTrue($posts['posts'][0]['isprivatereply']);
    2365  
    2366          // Another student will not receive the private reply.
    2367          self::setUser($student2);
    2368          $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
    2369          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2370          $this->assertEquals(1, count($posts['posts']));
    2371          $this->assertFalse($posts['posts'][0]['isprivatereply']);
    2372  
    2373          // A user cannot reply to a private reply.
    2374          self::setUser($teacher2);
    2375          $this->expectException('coding_exception');
    2376          $post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [
    2377                  'options' => [
    2378                      'name' => 'private',
    2379                      'value' => false,
    2380                  ],
    2381              ]);
    2382      }
    2383  
    2384      /**
    2385       * Test trusted text enabled.
    2386       */
    2387      public function test_trusted_text_enabled() {
    2388          global $USER, $CFG;
    2389  
    2390          $this->resetAfterTest(true);
    2391          $CFG->enabletrusttext = 1;
    2392  
    2393          $dangeroustext = '<button>Untrusted text</button>';
    2394          $cleantext = 'Untrusted text';
    2395  
    2396          // Create courses to add the modules.
    2397          $course = self::getDataGenerator()->create_course();
    2398          $user1 = self::getDataGenerator()->create_user();
    2399  
    2400          // First forum with tracking off.
    2401          $record = new \stdClass();
    2402          $record->course = $course->id;
    2403          $record->type = 'qanda';
    2404          $forum = self::getDataGenerator()->create_module('forum', $record);
    2405          $context = \context_module::instance($forum->cmid);
    2406  
    2407          // Add discussions to the forums.
    2408          $discussionrecord = new \stdClass();
    2409          $discussionrecord->course = $course->id;
    2410          $discussionrecord->userid = $user1->id;
    2411          $discussionrecord->forum = $forum->id;
    2412          $discussionrecord->message = $dangeroustext;
    2413          $discussionrecord->messagetrust  = trusttext_trusted($context);
    2414          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
    2415  
    2416          self::setAdminUser();
    2417          $discussionrecord->userid = $USER->id;
    2418          $discussionrecord->messagetrust  = trusttext_trusted($context);
    2419          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
    2420  
    2421          $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
    2422          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
    2423  
    2424          $this->assertCount(2, $discussions['discussions']);
    2425          $this->assertCount(0, $discussions['warnings']);
    2426          // Admin message is fully trusted.
    2427          $this->assertEquals(1, $discussions['discussions'][0]['messagetrust']);
    2428          $this->assertEquals($dangeroustext, $discussions['discussions'][0]['message']);
    2429          // Student message is not trusted.
    2430          $this->assertEquals(0, $discussions['discussions'][1]['messagetrust']);
    2431          $this->assertEquals($cleantext, $discussions['discussions'][1]['message']);
    2432  
    2433          // Get posts now.
    2434          $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
    2435          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2436          // Admin message is fully trusted.
    2437          $this->assertEquals(1, $posts['posts'][0]['messagetrust']);
    2438          $this->assertEquals($dangeroustext, $posts['posts'][0]['message']);
    2439  
    2440          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
    2441          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2442          // Student message is not trusted.
    2443          $this->assertEquals(0, $posts['posts'][0]['messagetrust']);
    2444          $this->assertEquals($cleantext, $posts['posts'][0]['message']);
    2445      }
    2446  
    2447      /**
    2448       * Test trusted text disabled.
    2449       */
    2450      public function test_trusted_text_disabled() {
    2451          global $USER, $CFG;
    2452  
    2453          $this->resetAfterTest(true);
    2454          $CFG->enabletrusttext = 0;
    2455  
    2456          $dangeroustext = '<button>Untrusted text</button>';
    2457          $cleantext = 'Untrusted text';
    2458  
    2459          // Create courses to add the modules.
    2460          $course = self::getDataGenerator()->create_course();
    2461          $user1 = self::getDataGenerator()->create_user();
    2462  
    2463          // First forum with tracking off.
    2464          $record = new \stdClass();
    2465          $record->course = $course->id;
    2466          $record->type = 'qanda';
    2467          $forum = self::getDataGenerator()->create_module('forum', $record);
    2468          $context = \context_module::instance($forum->cmid);
    2469  
    2470          // Add discussions to the forums.
    2471          $discussionrecord = new \stdClass();
    2472          $discussionrecord->course = $course->id;
    2473          $discussionrecord->userid = $user1->id;
    2474          $discussionrecord->forum = $forum->id;
    2475          $discussionrecord->message = $dangeroustext;
    2476          $discussionrecord->messagetrust = trusttext_trusted($context);
    2477          $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
    2478  
    2479          self::setAdminUser();
    2480          $discussionrecord->userid = $USER->id;
    2481          $discussionrecord->messagetrust = trusttext_trusted($context);
    2482          $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
    2483  
    2484          $discussions = mod_forum_external::get_forum_discussions($forum->id);
    2485          $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
    2486  
    2487          $this->assertCount(2, $discussions['discussions']);
    2488          $this->assertCount(0, $discussions['warnings']);
    2489          // Admin message is not trusted because enabletrusttext is disabled.
    2490          $this->assertEquals(0, $discussions['discussions'][0]['messagetrust']);
    2491          $this->assertEquals($cleantext, $discussions['discussions'][0]['message']);
    2492          // Student message is not trusted.
    2493          $this->assertEquals(0, $discussions['discussions'][1]['messagetrust']);
    2494          $this->assertEquals($cleantext, $discussions['discussions'][1]['message']);
    2495  
    2496          // Get posts now.
    2497          $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
    2498          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2499          // Admin message is not trusted because enabletrusttext is disabled.
    2500          $this->assertEquals(0, $posts['posts'][0]['messagetrust']);
    2501          $this->assertEquals($cleantext, $posts['posts'][0]['message']);
    2502  
    2503          $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
    2504          $posts = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
    2505          // Student message is not trusted.
    2506          $this->assertEquals(0, $posts['posts'][0]['messagetrust']);
    2507          $this->assertEquals($cleantext, $posts['posts'][0]['message']);
    2508      }
    2509  
    2510      /**
    2511       * Test delete a discussion.
    2512       */
    2513      public function test_delete_post_discussion() {
    2514          global $DB;
    2515          $this->resetAfterTest(true);
    2516  
    2517          // Setup test data.
    2518          $course = $this->getDataGenerator()->create_course();
    2519          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
    2520          $user = $this->getDataGenerator()->create_user();
    2521          $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
    2522          self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
    2523  
    2524          // Add a discussion.
    2525          $record = new \stdClass();
    2526          $record->course = $course->id;
    2527          $record->userid = $user->id;
    2528          $record->forum = $forum->id;
    2529          $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    2530  
    2531          $this->setUser($user);
    2532          $result = mod_forum_external::delete_post($discussion->firstpost);
    2533          $result = \external_api::clean_returnvalue(mod_forum_external::delete_post_returns(), $result);
    2534          $this->assertTrue($result['status']);
    2535          $this->assertEquals(0, $DB->count_records('forum_posts', array('id' => $discussion->firstpost)));
    2536          $this->assertEquals(0, $DB->count_records('forum_discussions', array('id' => $discussion->id)));
    2537      }
    2538  
    2539      /**
    2540       * Test delete a post.
    2541       */
    2542      public function test_delete_post_post() {
    2543          global $DB;
    2544          $this->resetAfterTest(true);
    2545  
    2546          // Setup test data.
    2547          $course = $this->getDataGenerator()->create_course();
    2548          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
    2549          $user = $this->getDataGenerator()->create_user();
    2550          $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
    2551          self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
    2552  
    2553          // Add a discussion.
    2554          $record = new \stdClass();
    2555          $record->course = $course->id;
    2556          $record->userid = $user->id;
    2557          $record->forum = $forum->id;
    2558          $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
    2559          $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
    2560  
    2561          // Add a post.
    2562          $record = new \stdClass();
    2563          $record->course = $course->id;
    2564          $record->userid = $user->id;
    2565          $record->forum = $forum->id;
    2566          $record->discussion = $discussion->id;
    2567          $record->parent = $parentpost->id;
    2568          $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
    2569  
    2570          $this->setUser($user);
    2571          $result = mod_forum_external::delete_post($post->id);
    2572          $result = \external_api::clean_returnvalue(mod_forum_external::delete_post_returns(), $result);
    2573          $this->assertTrue($result['status']);
    2574          $this->assertEquals(1, $DB->count_records('forum_posts', array('discussion' => $discussion->id)));
    2575          $this->assertEquals(1, $DB->count_records('forum_discussions', array('id' => $discussion->id)));
    2576      }
    2577  
    2578      /**
    2579       * Test delete a different user post.
    2580       */
    2581      public function test_delete_post_other_user_post() {
    2582          global $DB;
    2583          $this->resetAfterTest(true);
    2584  
    2585