Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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   * Renderer factory.
  19   *
  20   * @package    mod_forum
  21   * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_forum\local\factories;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use mod_forum\grades\forum_gradeitem;
  30  use mod_forum\local\entities\discussion as discussion_entity;
  31  use mod_forum\local\entities\forum as forum_entity;
  32  use mod_forum\local\factories\vault as vault_factory;
  33  use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
  34  use mod_forum\local\factories\entity as entity_factory;
  35  use mod_forum\local\factories\exporter as exporter_factory;
  36  use mod_forum\local\factories\manager as manager_factory;
  37  use mod_forum\local\factories\builder as builder_factory;
  38  use mod_forum\local\factories\url as url_factory;
  39  use mod_forum\local\renderers\discussion as discussion_renderer;
  40  use mod_forum\local\renderers\discussion_list as discussion_list_renderer;
  41  use mod_forum\local\renderers\posts as posts_renderer;
  42  use moodle_page;
  43  use core\output\notification;
  44  
  45  /**
  46   * Renderer factory.
  47   *
  48   * See:
  49   * https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
  50   *
  51   * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
  52   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  53   */
  54  class renderer {
  55      /** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
  56      private $legacydatamapperfactory;
  57      /** @var exporter_factory $exporterfactory Exporter factory */
  58      private $exporterfactory;
  59      /** @var vault_factory $vaultfactory Vault factory */
  60      private $vaultfactory;
  61      /** @var manager_factory $managerfactory Manager factory */
  62      private $managerfactory;
  63      /** @var entity_factory $entityfactory Entity factory */
  64      private $entityfactory;
  65      /** @var builder_factory $builderfactory Builder factory */
  66      private $builderfactory;
  67      /** @var url_factory $urlfactory URL factory */
  68      private $urlfactory;
  69      /** @var renderer_base $rendererbase Renderer base */
  70      private $rendererbase;
  71      /** @var moodle_page $page Moodle page */
  72      private $page;
  73  
  74      /**
  75       * Constructor.
  76       *
  77       * @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
  78       * @param exporter_factory $exporterfactory Exporter factory
  79       * @param vault_factory $vaultfactory Vault factory
  80       * @param manager_factory $managerfactory Manager factory
  81       * @param entity_factory $entityfactory Entity factory
  82       * @param builder_factory $builderfactory Builder factory
  83       * @param url_factory $urlfactory URL factory
  84       * @param moodle_page $page Moodle page
  85       */
  86      public function __construct(
  87          legacy_data_mapper_factory $legacydatamapperfactory,
  88          exporter_factory $exporterfactory,
  89          vault_factory $vaultfactory,
  90          manager_factory $managerfactory,
  91          entity_factory $entityfactory,
  92          builder_factory $builderfactory,
  93          url_factory $urlfactory,
  94          moodle_page $page
  95      ) {
  96          $this->legacydatamapperfactory = $legacydatamapperfactory;
  97          $this->exporterfactory = $exporterfactory;
  98          $this->vaultfactory = $vaultfactory;
  99          $this->managerfactory = $managerfactory;
 100          $this->entityfactory = $entityfactory;
 101          $this->builderfactory = $builderfactory;
 102          $this->urlfactory = $urlfactory;
 103          $this->page = $page;
 104          $this->rendererbase = $page->get_renderer('mod_forum');
 105      }
 106  
 107      /**
 108       * Create a discussion renderer for the given forum and discussion.
 109       *
 110       * @param forum_entity $forum Forum the discussion belongs to
 111       * @param discussion_entity $discussion Discussion to render
 112       * @param int $displaymode How should the posts be formatted?
 113       * @return discussion_renderer
 114       */
 115      public function get_discussion_renderer(
 116          forum_entity $forum,
 117          discussion_entity $discussion,
 118          int $displaymode
 119      ) : discussion_renderer {
 120  
 121          $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
 122          $ratingmanager = $this->managerfactory->get_rating_manager();
 123          $rendererbase = $this->rendererbase;
 124  
 125          $baseurl = $this->urlfactory->get_discussion_view_url_from_discussion($discussion);
 126          $notifications = [];
 127  
 128          return new discussion_renderer(
 129              $forum,
 130              $discussion,
 131              $displaymode,
 132              $rendererbase,
 133              $this->get_single_discussion_posts_renderer($displaymode, false),
 134              $this->page,
 135              $this->legacydatamapperfactory,
 136              $this->exporterfactory,
 137              $this->vaultfactory,
 138              $this->urlfactory,
 139              $this->entityfactory,
 140              $capabilitymanager,
 141              $ratingmanager,
 142              $this->entityfactory->get_exported_posts_sorter(),
 143              $baseurl,
 144              $notifications,
 145              function($discussion, $user, $forum) {
 146                  $exportbuilder = $this->builderfactory->get_exported_discussion_builder();
 147                  return $exportbuilder->build(
 148                      $user,
 149                      $forum,
 150                      $discussion
 151                  );
 152              }
 153          );
 154      }
 155  
 156      /**
 157       * Create a posts renderer to render posts without defined parent/reply relationships.
 158       *
 159       * @return posts_renderer
 160       */
 161      public function get_posts_renderer() : posts_renderer {
 162          return new posts_renderer(
 163              $this->rendererbase,
 164              $this->builderfactory->get_exported_posts_builder(),
 165              'mod_forum/forum_discussion_posts'
 166          );
 167      }
 168  
 169      /**
 170       * Create a posts renderer to render a list of posts in a single discussion.
 171       *
 172       * @param int|null $displaymode How should the posts be formatted?
 173       * @param bool $readonly Should the posts include the actions to reply, delete, etc?
 174       * @return posts_renderer
 175       */
 176      public function get_single_discussion_posts_renderer(int $displaymode = null, bool $readonly = false) : posts_renderer {
 177          $exportedpostssorter = $this->entityfactory->get_exported_posts_sorter();
 178  
 179          switch ($displaymode) {
 180              case FORUM_MODE_THREADED:
 181                  $template = 'mod_forum/forum_discussion_threaded_posts';
 182                  break;
 183              case FORUM_MODE_NESTED:
 184                  $template = 'mod_forum/forum_discussion_nested_posts';
 185                  break;
 186              case FORUM_MODE_NESTED_V2:
 187                  $template = 'mod_forum/forum_discussion_nested_v2_posts';
 188                  break;
 189              default;
 190                  $template = 'mod_forum/forum_discussion_posts';
 191                  break;
 192          }
 193  
 194          return new posts_renderer(
 195              $this->rendererbase,
 196              $this->builderfactory->get_exported_posts_builder(),
 197              $template,
 198              // Post process the exported posts for our template. This function will add the "replies"
 199              // and "hasreplies" properties to the exported posts. It will also sort them into the
 200              // reply tree structure if the display mode requires it.
 201              function($exportedposts, $forums, $discussions) use ($displaymode, $readonly, $exportedpostssorter) {
 202                  $forum = array_shift($forums);
 203                  $seenfirstunread = false;
 204                  $postcount = count($exportedposts);
 205                  $discussionsbyid = array_reduce($discussions, function($carry, $discussion) {
 206                      $carry[$discussion->get_id()] = $discussion;
 207                      return $carry;
 208                  }, []);
 209                  $exportedposts = array_map(
 210                      function($exportedpost) use ($forum, $discussionsbyid, $readonly, $seenfirstunread, $displaymode) {
 211                          $discussion = $discussionsbyid[$exportedpost->discussionid] ?? null;
 212                          if ($forum->get_type() == 'single' && !$exportedpost->hasparent) {
 213                              // Remove the author from any posts that don't have a parent.
 214                              unset($exportedpost->author);
 215                              unset($exportedpost->html['authorsubheading']);
 216                          }
 217  
 218                          $exportedpost->firstpost = false;
 219                          $exportedpost->readonly = $readonly;
 220                          $exportedpost->hasreplycount = false;
 221                          $exportedpost->hasreplies = false;
 222                          $exportedpost->replies = [];
 223                          $exportedpost->discussionlocked = $discussion ? $discussion->is_locked() : null;
 224  
 225                          $exportedpost->isfirstunread = false;
 226                          if (!$seenfirstunread && $exportedpost->unread) {
 227                              $exportedpost->isfirstunread = true;
 228                              $seenfirstunread = true;
 229                          }
 230  
 231                          if ($displaymode === FORUM_MODE_NESTED_V2) {
 232                              $exportedpost->showactionmenu = $exportedpost->capabilities['view'] ||
 233                                                              $exportedpost->capabilities['controlreadstatus'] ||
 234                                                              $exportedpost->capabilities['edit'] ||
 235                                                              $exportedpost->capabilities['split'] ||
 236                                                              $exportedpost->capabilities['delete'] ||
 237                                                              $exportedpost->capabilities['export'] ||
 238                                                              !empty($exportedpost->urls['viewparent']);
 239                          }
 240  
 241                          return $exportedpost;
 242                      },
 243                      $exportedposts
 244                  );
 245  
 246                  if (
 247                      $displaymode === FORUM_MODE_NESTED ||
 248                      $displaymode === FORUM_MODE_THREADED ||
 249                      $displaymode === FORUM_MODE_NESTED_V2
 250                  ) {
 251                      $sortedposts = $exportedpostssorter->sort_into_children($exportedposts);
 252                      $sortintoreplies = function($nestedposts) use (&$sortintoreplies) {
 253                          return array_map(function($postdata) use (&$sortintoreplies) {
 254                              [$post, $replies] = $postdata;
 255                              $totalreplycount = 0;
 256  
 257                              if (empty($replies)) {
 258                                  $post->replies = [];
 259                                  $post->hasreplies = false;
 260                              } else {
 261                                  $sortedreplies = $sortintoreplies($replies);
 262                                  // Set the parent author name on the replies. This is used for screen
 263                                  // readers to help them identify the structure of the discussion.
 264                                  $sortedreplies = array_map(function($reply) use ($post) {
 265                                      if (isset($post->author)) {
 266                                          $reply->parentauthorname = $post->author->fullname;
 267                                      } else {
 268                                          // The only time the author won't be set is for a single discussion
 269                                          // forum. See above for where it gets unset.
 270                                          $reply->parentauthorname = get_string('firstpost', 'mod_forum');
 271                                      }
 272                                      return $reply;
 273                                  }, $sortedreplies);
 274  
 275                                  $totalreplycount = array_reduce($sortedreplies, function($carry, $reply) {
 276                                      return $carry + 1 + $reply->totalreplycount;
 277                                  }, $totalreplycount);
 278  
 279                                  $post->replies = $sortedreplies;
 280                                  $post->hasreplies = true;
 281                              }
 282  
 283                              $post->totalreplycount = $totalreplycount;
 284  
 285                              return $post;
 286                          }, $nestedposts);
 287                      };
 288                      // Set the "replies" property on the exported posts.
 289                      $exportedposts = $sortintoreplies($sortedposts);
 290                  } else if ($displaymode === FORUM_MODE_FLATNEWEST || $displaymode === FORUM_MODE_FLATOLDEST) {
 291                      $exportedfirstpost = array_shift($exportedposts);
 292                      $exportedfirstpost->replies = $exportedposts;
 293                      $exportedfirstpost->hasreplies = true;
 294                      $exportedposts = [$exportedfirstpost];
 295                  }
 296  
 297                  if (!empty($exportedposts)) {
 298                      // Need to identify the first post so that we can use it in behat tests.
 299                      $exportedposts[0]->firstpost = true;
 300                      $exportedposts[0]->hasreplycount = true;
 301                      $exportedposts[0]->replycount = $postcount - 1;
 302                  }
 303  
 304                  return $exportedposts;
 305              }
 306          );
 307      }
 308  
 309      /**
 310       * Create a posts renderer to render posts in the forum search results.
 311       *
 312       * @param string[] $searchterms The search terms to be highlighted in the posts
 313       * @return posts_renderer
 314       */
 315      public function get_posts_search_results_renderer(array $searchterms) : posts_renderer {
 316          $urlfactory = $this->urlfactory;
 317  
 318          return new posts_renderer(
 319              $this->rendererbase,
 320              $this->builderfactory->get_exported_posts_builder(),
 321              'mod_forum/forum_search_results',
 322              // Post process the exported posts to add the highlighting of the search terms to the post
 323              // and also the additional context links in the subject.
 324              function($exportedposts, $forumsbyid, $discussionsbyid) use ($searchterms, $urlfactory) {
 325                  $highlightwords = implode(' ', $searchterms);
 326  
 327                  return array_map(
 328                      function($exportedpost) use (
 329                          $forumsbyid,
 330                          $discussionsbyid,
 331                          $searchterms,
 332                          $highlightwords,
 333                          $urlfactory
 334                      ) {
 335                          $discussion = $discussionsbyid[$exportedpost->discussionid];
 336                          $forum = $forumsbyid[$discussion->get_forum_id()];
 337  
 338                          $viewdiscussionurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
 339                          $exportedpost->urls['viewforum'] = $urlfactory->get_forum_view_url_from_forum($forum)->out(false);
 340                          $exportedpost->urls['viewdiscussion'] = $viewdiscussionurl->out(false);
 341                          $exportedpost->subject = highlight($highlightwords, $exportedpost->subject);
 342                          $exportedpost->forumname = format_string($forum->get_name(), true);
 343                          $exportedpost->discussionname = highlight($highlightwords, format_string($discussion->get_name(), true));
 344                          $exportedpost->showdiscussionname = $forum->get_type() != 'single';
 345  
 346                          // Identify search terms only found in HTML markup, and add a warning about them to
 347                          // the start of the message text. This logic was copied exactly as is from the previous
 348                          // implementation.
 349                          $missingterms = '';
 350                          $exportedpost->message = highlight(
 351                              $highlightwords,
 352                              $exportedpost->message,
 353                              0,
 354                              '<fgw9sdpq4>',
 355                              '</fgw9sdpq4>'
 356                          );
 357  
 358                          foreach ($searchterms as $searchterm) {
 359                              if (
 360                                  preg_match("/$searchterm/i", $exportedpost->message) &&
 361                                  !preg_match('/<fgw9sdpq4>' . $searchterm . '<\/fgw9sdpq4>/i', $exportedpost->message)
 362                              ) {
 363                                  $missingterms .= " $searchterm";
 364                              }
 365                          }
 366  
 367                          $exportedpost->message = str_replace('<fgw9sdpq4>', '<span class="highlight">', $exportedpost->message);
 368                          $exportedpost->message = str_replace('</fgw9sdpq4>', '</span>', $exportedpost->message);
 369  
 370                          if ($missingterms) {
 371                              $strmissingsearchterms = get_string('missingsearchterms', 'forum');
 372                              $exportedpost->message = '<p class="highlight2">' . $strmissingsearchterms . ' '
 373                                  . $missingterms . '</p>' . $exportedpost->message;
 374                          }
 375  
 376                          return $exportedpost;
 377                      },
 378                      $exportedposts
 379                  );
 380              }
 381          );
 382      }
 383  
 384      /**
 385       * Create a posts renderer to render posts in mod/forum/user.php.
 386       *
 387       * @param bool $addlinkstocontext Should links to the course, forum, and discussion be included?
 388       * @return posts_renderer
 389       */
 390      public function get_user_forum_posts_report_renderer(bool $addlinkstocontext) : posts_renderer {
 391          $urlfactory = $this->urlfactory;
 392  
 393          return new posts_renderer(
 394              $this->rendererbase,
 395              $this->builderfactory->get_exported_posts_builder(),
 396              'mod_forum/forum_posts_with_context_links',
 397              function($exportedposts, $forumsbyid, $discussionsbyid) use ($urlfactory, $addlinkstocontext) {
 398  
 399                  return array_map(function($exportedpost) use ($forumsbyid, $discussionsbyid, $addlinkstocontext, $urlfactory) {
 400                      $discussion = $discussionsbyid[$exportedpost->discussionid];
 401                      $forum = $forumsbyid[$discussion->get_forum_id()];
 402                      $courserecord = $forum->get_course_record();
 403  
 404                      if ($addlinkstocontext) {
 405                          $viewdiscussionurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
 406                          $exportedpost->urls['viewforum'] = $urlfactory->get_forum_view_url_from_forum($forum)->out(false);
 407                          $exportedpost->urls['viewdiscussion'] = $viewdiscussionurl->out(false);
 408                          $exportedpost->urls['viewcourse'] = $urlfactory->get_course_url_from_forum($forum)->out(false);
 409                      }
 410  
 411                      $exportedpost->forumname = format_string($forum->get_name(), true);
 412                      $exportedpost->discussionname = format_string($discussion->get_name(), true);
 413                      $exportedpost->coursename = format_string($courserecord->shortname, true);
 414                      $exportedpost->showdiscussionname = $forum->get_type() != 'single';
 415  
 416                      return $exportedpost;
 417                  }, $exportedposts);
 418              }
 419          );
 420      }
 421  
 422      /**
 423       * Create a standard type discussion list renderer.
 424       *
 425       * @param forum_entity $forum The forum that the discussions belong to
 426       * @return discussion_list_renderer
 427       */
 428      public function get_discussion_list_renderer(
 429          forum_entity $forum
 430      ) : discussion_list_renderer {
 431  
 432          $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
 433          $rendererbase = $this->rendererbase;
 434          $notifications = [];
 435  
 436          switch ($forum->get_type()) {
 437              case 'news':
 438                  if (SITEID == $forum->get_course_id()) {
 439                      $template = 'mod_forum/frontpage_news_discussion_list';
 440                  } else {
 441                      $template = 'mod_forum/news_discussion_list';
 442                  }
 443                  break;
 444              case 'qanda':
 445                  $template = 'mod_forum/qanda_discussion_list';
 446                  break;
 447              default:
 448                  $template = 'mod_forum/discussion_list';
 449          }
 450  
 451          return new discussion_list_renderer(
 452              $forum,
 453              $rendererbase,
 454              $this->legacydatamapperfactory,
 455              $this->exporterfactory,
 456              $this->vaultfactory,
 457              $this->builderfactory,
 458              $capabilitymanager,
 459              $this->urlfactory,
 460              forum_gradeitem::load_from_forum_entity($forum),
 461              $template,
 462              $notifications,
 463              function($discussions, $user, $forum) {
 464  
 465                  $exporteddiscussionsummarybuilder = $this->builderfactory->get_exported_discussion_summaries_builder();
 466                  return $exporteddiscussionsummarybuilder->build(
 467                      $user,
 468                      $forum,
 469                      $discussions
 470                  );
 471              }
 472          );
 473      }
 474  
 475      /**
 476       * Create a discussion list renderer which shows more information about the first post.
 477       *
 478       * @param forum_entity $forum The forum that the discussions belong to
 479       * @param string $template The template to use
 480       * @return discussion_list_renderer
 481       */
 482      private function get_detailed_discussion_list_renderer(
 483          forum_entity $forum,
 484          string $template
 485      ) : discussion_list_renderer {
 486  
 487          $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
 488          $rendererbase = $this->rendererbase;
 489          $notifications = [];
 490  
 491          return new discussion_list_renderer(
 492              $forum,
 493              $rendererbase,
 494              $this->legacydatamapperfactory,
 495              $this->exporterfactory,
 496              $this->vaultfactory,
 497              $this->builderfactory,
 498              $capabilitymanager,
 499              $this->urlfactory,
 500              forum_gradeitem::load_from_forum_entity($forum),
 501              $template,
 502              $notifications,
 503              function($discussions, $user, $forum) use ($capabilitymanager) {
 504                  $exportedpostsbuilder = $this->builderfactory->get_exported_posts_builder();
 505                  $discussionentries = [];
 506                  $postentries = [];
 507                  foreach ($discussions as $discussion) {
 508                      $discussionentries[] = $discussion->get_discussion();
 509                      $discussionentriesids[] = $discussion->get_discussion()->get_id();
 510                      $postentries[] = $discussion->get_first_post();
 511                  }
 512  
 513                  $exportedposts['posts'] = $exportedpostsbuilder->build(
 514                      $user,
 515                      [$forum],
 516                      $discussionentries,
 517                      $postentries
 518                  );
 519  
 520                  $postvault = $this->vaultfactory->get_post_vault();
 521                  $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
 522                  $discussionrepliescount = $postvault->get_reply_count_for_discussion_ids(
 523                          $user,
 524                          $discussionentriesids,
 525                          $canseeanyprivatereply
 526                      );
 527                  $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
 528                  $forumrecord = $forumdatamapper->to_legacy_object($forum);
 529                  if (forum_tp_is_tracked($forumrecord, $user)) {
 530                      $discussionunreadscount = $postvault->get_unread_count_for_discussion_ids(
 531                              $user,
 532                              $discussionentriesids,
 533                              $canseeanyprivatereply
 534                      );
 535                  } else {
 536                      $discussionunreadscount = [];
 537                  }
 538  
 539                  array_walk($exportedposts['posts'], function($post) use ($discussionrepliescount, $discussionunreadscount) {
 540                      $post->discussionrepliescount = $discussionrepliescount[$post->discussionid] ?? 0;
 541                      $post->discussionunreadscount = $discussionunreadscount[$post->discussionid] ?? 0;
 542                      // TODO: Find a better solution due to language differences when defining the singular and plural form.
 543                      $post->isreplyplural = $post->discussionrepliescount != 1 ? true : false;
 544                      $post->isunreadplural = $post->discussionunreadscount != 1 ? true : false;
 545                  });
 546  
 547                  $exportedposts['state']['hasdiscussions'] = $exportedposts['posts'] ? true : false;
 548  
 549                  return $exportedposts;
 550              }
 551          );
 552      }
 553  
 554      /**
 555       * Create a blog type discussion list renderer.
 556       *
 557       * @param forum_entity $forum The forum that the discussions belong to
 558       * @return discussion_list_renderer
 559       */
 560      public function get_blog_discussion_list_renderer(
 561          forum_entity $forum
 562      ) : discussion_list_renderer {
 563          return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/blog_discussion_list');
 564      }
 565  
 566      /**
 567       * Create a discussion list renderer for the social course format.
 568       *
 569       * @param forum_entity $forum The forum that the discussions belong to
 570       * @return discussion_list_renderer
 571       */
 572      public function get_social_discussion_list_renderer(
 573          forum_entity $forum
 574      ) : discussion_list_renderer {
 575          return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/social_discussion_list');
 576      }
 577  
 578      /**
 579       * Create a discussion list renderer for the social course format.
 580       *
 581       * @param forum_entity $forum The forum that the discussions belong to
 582       * @return discussion_list_renderer
 583       */
 584      public function get_frontpage_news_discussion_list_renderer(
 585          forum_entity $forum
 586      ) : discussion_list_renderer {
 587          return $this->get_detailed_discussion_list_renderer($forum, 'mod_forum/frontpage_social_discussion_list');
 588      }
 589  
 590      /**
 591       * Create a single type discussion list renderer.
 592       *
 593       * @param forum_entity $forum Forum the discussion belongs to
 594       * @param discussion_entity $discussion The discussion entity
 595       * @param bool $hasmultiplediscussions Whether the forum has multiple discussions (more than one)
 596       * @param int $displaymode How should the posts be formatted?
 597       * @return discussion_renderer
 598       */
 599      public function get_single_discussion_list_renderer(
 600          forum_entity $forum,
 601          discussion_entity $discussion,
 602          bool $hasmultiplediscussions,
 603          int $displaymode
 604      ) : discussion_renderer {
 605  
 606          $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
 607          $ratingmanager = $this->managerfactory->get_rating_manager();
 608          $rendererbase = $this->rendererbase;
 609  
 610          $cmid = $forum->get_course_module_record()->id;
 611          $baseurl = $this->urlfactory->get_forum_view_url_from_course_module_id($cmid);
 612          $notifications = array();
 613  
 614          if ($hasmultiplediscussions) {
 615              $notifications[] = (new notification(get_string('warnformorepost', 'forum')))
 616                  ->set_show_closebutton(true);
 617          }
 618  
 619          return new discussion_renderer(
 620              $forum,
 621              $discussion,
 622              $displaymode,
 623              $rendererbase,
 624              $this->get_single_discussion_posts_renderer($displaymode, false),
 625              $this->page,
 626              $this->legacydatamapperfactory,
 627              $this->exporterfactory,
 628              $this->vaultfactory,
 629              $this->urlfactory,
 630              $this->entityfactory,
 631              $capabilitymanager,
 632              $ratingmanager,
 633              $this->entityfactory->get_exported_posts_sorter(),
 634              $baseurl,
 635              $notifications
 636          );
 637      }
 638  }