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 37 and 311]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * 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  }