Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Exported discussion summaries builder class.
  19   *
  20   * @package    mod_forum
  21   * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_forum\local\builders;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use mod_forum\local\entities\discussion as discussion_entity;
  30  use mod_forum\local\entities\forum as forum_entity;
  31  use mod_forum\local\entities\post as post_entity;
  32  use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
  33  use mod_forum\local\factories\exporter as exporter_factory;
  34  use mod_forum\local\factories\vault as vault_factory;
  35  use mod_forum\local\factories\manager as manager_factory;
  36  use rating_manager;
  37  use renderer_base;
  38  use stdClass;
  39  
  40  /**
  41   * Exported discussion summaries builder class.
  42   *
  43   * This class is an implementation of the builder pattern (loosely). It is responsible
  44   * for taking a set of related forums, discussions, and posts and generate the exported
  45   * version of the discussion summaries.
  46   *
  47   * It encapsulates the complexity involved with exporting discussions summaries. All of the relevant
  48   * additional resources will be loaded by this class in order to ensure the exporting
  49   * process can happen.
  50   *
  51   * See this doc for more information on the builder pattern:
  52   * https://designpatternsphp.readthedocs.io/en/latest/Creational/Builder/README.html
  53   *
  54   * @package    mod_forum
  55   * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
  56   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  57   */
  58  class exported_discussion_summaries {
  59      /** @var renderer_base $renderer Core renderer */
  60      private $renderer;
  61  
  62      /** @var legacy_data_mapper_factory $legacydatamapperfactory Data mapper factory */
  63      private $legacydatamapperfactory;
  64  
  65      /** @var exporter_factory $exporterfactory Exporter factory */
  66      private $exporterfactory;
  67  
  68      /** @var vault_factory $vaultfactory Vault factory */
  69      private $vaultfactory;
  70  
  71      /** @var manager_factory $managerfactory Manager factory */
  72      private $managerfactory;
  73  
  74      /** @var rating_manager $ratingmanager Rating manager */
  75      private $ratingmanager;
  76  
  77      /**
  78       * Constructor.
  79       *
  80       * @param renderer_base $renderer Core renderer
  81       * @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
  82       * @param exporter_factory $exporterfactory Exporter factory
  83       * @param vault_factory $vaultfactory Vault factory
  84       * @param manager_factory $managerfactory Manager factory
  85       */
  86      public function __construct(
  87          renderer_base $renderer,
  88          legacy_data_mapper_factory $legacydatamapperfactory,
  89          exporter_factory $exporterfactory,
  90          vault_factory $vaultfactory,
  91          manager_factory $managerfactory
  92      ) {
  93          $this->renderer = $renderer;
  94          $this->legacydatamapperfactory = $legacydatamapperfactory;
  95          $this->exporterfactory = $exporterfactory;
  96          $this->vaultfactory = $vaultfactory;
  97          $this->managerfactory = $managerfactory;
  98          $this->ratingmanager = $managerfactory->get_rating_manager();
  99      }
 100  
 101      /**
 102       * Build the exported discussion summaries for a given set of discussions.
 103       *
 104       * This will typically be used for a list of discussions in the same forum.
 105       *
 106       * @param stdClass $user The user to export the posts for.
 107       * @param forum_entity $forum The forum that each of the $discussions belong to
 108       * @param discussion_summary_entity[] $discussions A list of all discussion summaries to export
 109       * @return stdClass[] List of exported posts in the same order as the $posts array.
 110       */
 111      public function build(
 112          stdClass $user,
 113          forum_entity $forum,
 114          array $discussions
 115      ) : array {
 116          $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
 117          $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
 118  
 119          $discussionids = array_keys($discussions);
 120  
 121          $postvault = $this->vaultfactory->get_post_vault();
 122          $posts = $postvault->get_from_discussion_ids($user, $discussionids, $canseeanyprivatereply);
 123          $groupsbyid = $this->get_groups_available_in_forum($forum);
 124          $groupsbyauthorid = $this->get_author_groups_from_posts($posts, $forum);
 125  
 126          $replycounts = $postvault->get_reply_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
 127          $latestposts = $postvault->get_latest_posts_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
 128          $latestauthors = $this->get_latest_posts_authors($latestposts);
 129          $latestpostsids = array_map(function($post) {
 130              return $post->get_id();
 131          }, $latestposts);
 132  
 133          $postauthorids = array_unique(array_reduce($discussions, function($carry, $summary) use ($latestposts){
 134              $firstpostauthorid = $summary->get_first_post_author()->get_id();
 135              $discussion = $summary->get_discussion();
 136              $lastpostauthorid = $latestposts[$discussion->get_id()]->get_author_id();
 137              return array_merge($carry, [$firstpostauthorid, $lastpostauthorid]);
 138          }, []));
 139          $postauthorcontextids = $this->get_author_context_ids($postauthorids);
 140  
 141          $unreadcounts = [];
 142          $favourites = $this->get_favourites($user);
 143          $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
 144          $forumrecord = $forumdatamapper->to_legacy_object($forum);
 145  
 146          if (forum_tp_can_track_forums($forumrecord)) {
 147              $unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
 148          }
 149  
 150          $summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
 151              $user,
 152              $forum,
 153              $discussions,
 154              $groupsbyid,
 155              $groupsbyauthorid,
 156              $replycounts,
 157              $unreadcounts,
 158              $latestpostsids,
 159              $postauthorcontextids,
 160              $favourites,
 161              $latestauthors
 162          );
 163  
 164          $exportedposts = (array) $summaryexporter->export($this->renderer);
 165          $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
 166  
 167          array_walk($exportedposts['summaries'], function($summary) use ($firstposts, $latestposts) {
 168              $summary->discussion->times['created'] = (int) $firstposts[$summary->discussion->firstpostid]->get_time_created();
 169              $summary->discussion->times['modified'] = (int) $latestposts[$summary->discussion->id]->get_time_created();
 170          });
 171  
 172          // Pass the current, preferred sort order for the discussions list.
 173          $discussionlistvault = $this->vaultfactory->get_discussions_in_forum_vault();
 174          $sortorder = get_user_preferences('forum_discussionlistsortorder',
 175              $discussionlistvault::SORTORDER_LASTPOST_DESC);
 176  
 177          $sortoptions = array(
 178              'islastpostdesc' => $sortorder == $discussionlistvault::SORTORDER_LASTPOST_DESC,
 179              'islastpostasc' => $sortorder == $discussionlistvault::SORTORDER_LASTPOST_ASC,
 180              'isrepliesdesc' => $sortorder == $discussionlistvault::SORTORDER_REPLIES_DESC,
 181              'isrepliesasc' => $sortorder == $discussionlistvault::SORTORDER_REPLIES_ASC,
 182              'iscreateddesc' => $sortorder == $discussionlistvault::SORTORDER_CREATED_DESC,
 183              'iscreatedasc' => $sortorder == $discussionlistvault::SORTORDER_CREATED_ASC,
 184              'isdiscussiondesc' => $sortorder == $discussionlistvault::SORTORDER_DISCUSSION_DESC,
 185              'isdiscussionasc' => $sortorder == $discussionlistvault::SORTORDER_DISCUSSION_ASC,
 186              'isstarterdesc' => $sortorder == $discussionlistvault::SORTORDER_STARTER_DESC,
 187              'isstarterasc' => $sortorder == $discussionlistvault::SORTORDER_STARTER_ASC,
 188              'isgroupdesc' => $sortorder == $discussionlistvault::SORTORDER_GROUP_DESC,
 189              'isgroupasc' => $sortorder == $discussionlistvault::SORTORDER_GROUP_ASC,
 190          );
 191  
 192          $exportedposts['state']['sortorder'] = $sortoptions;
 193  
 194          return $exportedposts;
 195      }
 196  
 197      /**
 198       * Get a list of all favourited discussions.
 199       *
 200       * @param stdClass $user The user we are getting favourites for
 201       * @return int[] A list of favourited itemids
 202       */
 203      private function get_favourites(stdClass $user) : array {
 204          $ids = [];
 205  
 206          if (isloggedin()) {
 207              $usercontext = \context_user::instance($user->id);
 208              $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
 209              $favourites = $ufservice->find_favourites_by_type('mod_forum', 'discussions');
 210              foreach ($favourites as $favourite) {
 211                  $ids[] = $favourite->itemid;
 212              }
 213          }
 214  
 215          return $ids;
 216      }
 217  
 218      /**
 219       * Returns a mapped array of discussionid to the authors of the latest post
 220       *
 221       * @param array $latestposts Mapped array of discussion to latest posts.
 222       * @return array Array of authors mapped to the discussion
 223       */
 224      private function get_latest_posts_authors($latestposts) {
 225          $authors = $this->vaultfactory->get_author_vault()->get_authors_for_posts($latestposts);
 226  
 227          $mappedauthors = array_reduce($latestposts, function($carry, $item) use ($authors) {
 228              $carry[$item->get_discussion_id()] = $authors[$item->get_author_id()];
 229  
 230              return $carry;
 231          }, []);
 232          return $mappedauthors;
 233      }
 234  
 235      /**
 236       * Get the groups details for all groups available to the forum.
 237       * @param forum_entity $forum The forum entity
 238       * @return stdClass[]
 239       */
 240      private function get_groups_available_in_forum($forum) : array {
 241          $course = $forum->get_course_record();
 242          $coursemodule = $forum->get_course_module_record();
 243  
 244          return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
 245      }
 246  
 247      /**
 248       * Get the author's groups for a list of posts.
 249       *
 250       * @param post_entity[] $posts The list of posts
 251       * @param forum_entity $forum The forum entity
 252       * @return array Author groups indexed by author id
 253       */
 254      private function get_author_groups_from_posts(array $posts, $forum) : array {
 255          $course = $forum->get_course_record();
 256          $coursemodule = $forum->get_course_module_record();
 257          $authorids = array_reduce($posts, function($carry, $post) {
 258              $carry[$post->get_author_id()] = true;
 259              return $carry;
 260          }, []);
 261          $authorgroups = groups_get_all_groups($course->id, array_keys($authorids), $coursemodule->groupingid,
 262                  'g.*, gm.id, gm.groupid, gm.userid');
 263  
 264          $authorgroups = array_reduce($authorgroups, function($carry, $group) {
 265              // Clean up data returned from groups_get_all_groups.
 266              $userid = $group->userid;
 267              $groupid = $group->groupid;
 268  
 269              unset($group->userid);
 270              unset($group->groupid);
 271              $group->id = $groupid;
 272  
 273              if (!isset($carry[$userid])) {
 274                  $carry[$userid] = [$group];
 275              } else {
 276                  $carry[$userid][] = $group;
 277              }
 278  
 279              return $carry;
 280          }, []);
 281  
 282          foreach (array_diff(array_keys($authorids), array_keys($authorgroups)) as $authorid) {
 283              $authorgroups[$authorid] = [];
 284          }
 285  
 286          return $authorgroups;
 287      }
 288  
 289      /**
 290       * Get the user context ids for each of the authors.
 291       *
 292       * @param int[] $authorids The list of author ids to fetch context ids for.
 293       * @return int[] Context ids indexed by author id
 294       */
 295      private function get_author_context_ids(array $authorids) : array {
 296          $authorvault = $this->vaultfactory->get_author_vault();
 297          return $authorvault->get_context_ids_for_author_ids($authorids);
 298      }
 299  }