Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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   * Discussion list renderer.
  19   *
  20   * @package    mod_forum
  21   * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_forum\local\renderers;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use mod_forum\grades\forum_gradeitem;
  30  use mod_forum\local\entities\forum as forum_entity;
  31  use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
  32  use mod_forum\local\factories\exporter as exporter_factory;
  33  use mod_forum\local\factories\vault as vault_factory;
  34  use mod_forum\local\factories\url as url_factory;
  35  use mod_forum\local\managers\capability as capability_manager;
  36  use mod_forum\local\vaults\discussion_list as discussion_list_vault;
  37  use renderer_base;
  38  use stdClass;
  39  use core\output\notification;
  40  use mod_forum\local\data_mappers\legacy\forum;
  41  use mod_forum\local\factories\builder as builder_factory;
  42  
  43  require_once($CFG->dirroot . '/mod/forum/lib.php');
  44  
  45  /**
  46   * The discussion list renderer.
  47   *
  48   * @package    mod_forum
  49   * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
  50   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51   */
  52  class discussion_list {
  53      /** @var forum_entity The forum being rendered */
  54      private $forum;
  55  
  56      /** @var stdClass The DB record for the forum being rendered */
  57      private $forumrecord;
  58  
  59      /** @var renderer_base The renderer used to render the view */
  60      private $renderer;
  61  
  62      /** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy 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 capability_manager $capabilitymanager Capability manager */
  72      private $capabilitymanager;
  73  
  74      /** @var url_factory $urlfactory URL factory */
  75      private $urlfactory;
  76  
  77      /** @var array $notifications List of notification HTML */
  78      private $notifications;
  79  
  80      /** @var builder_factory $builderfactory Builder factory */
  81      private $builderfactory;
  82  
  83      /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
  84      private $postprocessfortemplate;
  85  
  86      /** @var string $template The template to use when displaying */
  87      private $template;
  88  
  89      /** @var gradeitem The gradeitem instance associated with this forum */
  90      private $forumgradeitem;
  91  
  92      /**
  93       * Constructor for a new discussion list renderer.
  94       *
  95       * @param   forum_entity        $forum The forum entity to be rendered
  96       * @param   renderer_base       $renderer The renderer used to render the view
  97       * @param   legacy_data_mapper_factory $legacydatamapperfactory The factory used to fetch a legacy record
  98       * @param   exporter_factory    $exporterfactory The factory used to fetch exporter instances
  99       * @param   vault_factory       $vaultfactory The factory used to fetch the vault instances
 100       * @param   builder_factory     $builderfactory The factory used to fetch the builder instances
 101       * @param   capability_manager  $capabilitymanager The managed used to check capabilities on the forum
 102       * @param   url_factory         $urlfactory The factory used to create URLs in the forum
 103       * @param   string              $template
 104       * @param   notification[]      $notifications A list of any notifications to be displayed within the page
 105       * @param   callable|null       $postprocessfortemplate Callback function to process discussion lists for templates
 106       */
 107      public function __construct(
 108          forum_entity $forum,
 109          renderer_base $renderer,
 110          legacy_data_mapper_factory $legacydatamapperfactory,
 111          exporter_factory $exporterfactory,
 112          vault_factory $vaultfactory,
 113          builder_factory $builderfactory,
 114          capability_manager $capabilitymanager,
 115          url_factory $urlfactory,
 116          forum_gradeitem $forumgradeitem,
 117          string $template,
 118          array $notifications = [],
 119          callable $postprocessfortemplate = null
 120      ) {
 121          $this->forum = $forum;
 122          $this->renderer = $renderer;
 123          $this->legacydatamapperfactory = $legacydatamapperfactory;
 124          $this->exporterfactory = $exporterfactory;
 125          $this->vaultfactory = $vaultfactory;
 126          $this->builderfactory = $builderfactory;
 127          $this->capabilitymanager = $capabilitymanager;
 128  
 129          $this->urlfactory = $urlfactory;
 130          $this->notifications = $notifications;
 131          $this->postprocessfortemplate = $postprocessfortemplate;
 132          $this->template = $template;
 133          $this->forumgradeitem = $forumgradeitem;
 134  
 135          $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
 136          $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
 137      }
 138  
 139      /**
 140       * Render for the specified user.
 141       *
 142       * @param   stdClass    $user The user to render for
 143       * @param   cm_info     $cm The course module info for this discussion list
 144       * @param   int         $groupid The group to render
 145       * @param   int         $sortorder The sort order to use when selecting the discussions in the list
 146       * @param   int         $pageno The zero-indexed page number to use
 147       * @param   int         $pagesize The number of discussions to show on the page
 148       * @param   int         $displaymode The discussion display mode
 149       * @param   bool        $enablediscussioncreation To show the discussion button.
 150       * @return  string      The rendered content for display
 151       */
 152      public function render(
 153          stdClass $user,
 154          \cm_info $cm,
 155          ?int $groupid,
 156          ?int $sortorder,
 157          ?int $pageno,
 158          ?int $pagesize,
 159          int $displaymode = null,
 160          bool $enablediscussioncreation = true
 161      ) : string {
 162          global $PAGE;
 163  
 164          $forum = $this->forum;
 165          $course = $forum->get_course_record();
 166  
 167          $forumexporter = $this->exporterfactory->get_forum_exporter(
 168              $user,
 169              $this->forum,
 170              $groupid
 171          );
 172  
 173          $pagesize = $this->get_page_size($pagesize);
 174          $pageno = $this->get_page_number($pageno);
 175  
 176          // Count all forum discussion posts.
 177          $alldiscussionscount = mod_forum_count_all_discussions($forum, $user, $groupid);
 178  
 179          // Get all forum discussion summaries.
 180          $discussions = mod_forum_get_discussion_summaries($forum, $user, $groupid, $sortorder, $pageno, $pagesize);
 181  
 182          $capabilitymanager = $this->capabilitymanager;
 183          $hasanyactions = false;
 184          $hasanyactions = $hasanyactions || $capabilitymanager->can_favourite_discussion($user);
 185          $hasanyactions = $hasanyactions || $capabilitymanager->can_pin_discussions($user);
 186          $hasanyactions = $hasanyactions || $capabilitymanager->can_manage_forum($user);
 187  
 188          $forumview = [
 189              'forum' => (array) $forumexporter->export($this->renderer),
 190              'contextid' => $forum->get_context()->id,
 191              'cmid' => $cm->id,
 192              'groupid' => $groupid,
 193              'name' => format_string($forum->get_name()),
 194              'courseid' => $course->id,
 195              'coursename' => format_string($course->shortname),
 196              'experimentaldisplaymode' => $displaymode == FORUM_MODE_NESTED_V2,
 197              'gradingcomponent' => $this->forumgradeitem->get_grading_component_name(),
 198              'gradingcomponentsubtype' => $this->forumgradeitem->get_grading_component_subtype(),
 199              'sendstudentnotifications' => $forum->should_notify_students_default_when_grade_for_forum(),
 200              'gradeonlyactiveusers' => $this->forumgradeitem->should_grade_only_active_users(),
 201              'hasanyactions' => $hasanyactions,
 202              'groupchangemenu' => groups_print_activity_menu(
 203                  $cm,
 204                  $this->urlfactory->get_forum_view_url_from_forum($forum),
 205                  true
 206              ),
 207              'hasmore' => ($alldiscussionscount > $pagesize),
 208              'notifications' => $this->get_notifications($user, $groupid),
 209              'settings' => [
 210                  'excludetext' => true,
 211                  'togglemoreicon' => true,
 212                  'excludesubscription' => true
 213              ],
 214              'totaldiscussioncount' => $alldiscussionscount,
 215              'userid' => $user->id,
 216              'visiblediscussioncount' => count($discussions),
 217              'enablediscussioncreation' => $enablediscussioncreation,
 218          ];
 219  
 220          if ($forumview['forum']['capabilities']['create']) {
 221              $forumview['newdiscussionhtml'] = $this->get_discussion_form($user, $cm, $groupid);
 222          }
 223  
 224          if (!$discussions) {
 225              return $this->renderer->render_from_template($this->template, $forumview);
 226          }
 227  
 228          if ($this->postprocessfortemplate !== null) {
 229              // We've got some post processing to do!
 230              $exportedposts = ($this->postprocessfortemplate) ($discussions, $user, $forum);
 231          }
 232  
 233          $baseurl = new \moodle_url($PAGE->url, array('o' => $sortorder));
 234  
 235          $forumview = array_merge(
 236              $forumview,
 237              [
 238                  'pagination' => $this->renderer->render(new \paging_bar($alldiscussionscount, $pageno, $pagesize, $baseurl, 'p')),
 239              ],
 240              $exportedposts
 241          );
 242  
 243          $firstdiscussion = reset($discussions);
 244          $forumview['firstgradeduserid'] = $firstdiscussion->get_latest_post_author()->get_id();
 245  
 246          return $this->renderer->render_from_template($this->template, $forumview);
 247      }
 248  
 249      /**
 250       * Add new discussion button to the action bar for tertiary nav.
 251       *
 252       * @param stdClass $user The user object.
 253       * @param int|null $groupid The group id.
 254       * @return string rendered HTML string
 255       */
 256      public function render_new_discussion(stdClass $user, ?int $groupid): string {
 257          $forumexporter = $this->exporterfactory->get_forum_exporter(
 258              $user,
 259              $this->forum,
 260              $groupid
 261          );
 262  
 263          $forumview = [
 264              'forum' => (array) $forumexporter->export($this->renderer),
 265          ];
 266  
 267          return $this->renderer->render_from_template('mod_forum/forum_new_discussion_actionbar', $forumview);
 268      }
 269  
 270      /**
 271       * Get the mod_forum_post_form. This is the default boiler plate from mod_forum/post_form.php with the inpage flag caveat
 272       *
 273       * @param stdClass $user The user the form is being generated for
 274       * @param \cm_info $cm
 275       * @param int $groupid The groupid if any
 276       *
 277       * @return string The rendered html
 278       */
 279      private function get_discussion_form(stdClass $user, \cm_info $cm, ?int $groupid) {
 280          $forum = $this->forum;
 281          $forumrecord = $this->legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
 282          $modcontext = \context_module::instance($cm->id);
 283          $coursecontext = \context_course::instance($forum->get_course_id());
 284          $post = (object) [
 285              'course' => $forum->get_course_id(),
 286              'forum' => $forum->get_id(),
 287              'discussion' => 0,           // Ie discussion # not defined yet.
 288              'parent' => 0,
 289              'subject' => '',
 290              'userid' => $user->id,
 291              'message' => '',
 292              'messageformat' => editors_get_preferred_format(),
 293              'messagetrust' => 0,
 294              'groupid' => $groupid,
 295          ];
 296          $thresholdwarning = forum_check_throttling($forumrecord, $cm);
 297  
 298          $formparams = array(
 299              'course' => $forum->get_course_record(),
 300              'cm' => $cm,
 301              'coursecontext' => $coursecontext,
 302              'modcontext' => $modcontext,
 303              'forum' => $forumrecord,
 304              'post' => $post,
 305              'subscribe' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord,
 306                  null, $cm),
 307              'thresholdwarning' => $thresholdwarning,
 308              'inpagereply' => true,
 309              'edit' => 0
 310          );
 311          $posturl = new \moodle_url('/mod/forum/post.php');
 312          $mformpost = new \mod_forum_post_form($posturl, $formparams, 'post', '', array('id' => 'mformforum'));
 313          $discussionsubscribe = \mod_forum\subscriptions::get_user_default_subscription($forumrecord, $coursecontext, $cm, null);
 314  
 315          $params = array('reply' => 0, 'forum' => $forumrecord->id, 'edit' => 0) +
 316              (isset($post->groupid) ? array('groupid' => $post->groupid) : array()) +
 317              array(
 318                  'userid' => $post->userid,
 319                  'parent' => $post->parent,
 320                  'discussion' => $post->discussion,
 321                  'course' => $forum->get_course_id(),
 322                  'discussionsubscribe' => $discussionsubscribe
 323              );
 324          $mformpost->set_data($params);
 325  
 326          return $mformpost->render();
 327      }
 328  
 329      /**
 330       * Fetch the page size to use when displaying the page.
 331       *
 332       * @param   int         $pagesize The number of discussions to show on the page
 333       * @return  int         The normalised page size
 334       */
 335      private function get_page_size(?int $pagesize) : int {
 336          if (null === $pagesize || $pagesize <= 0) {
 337              $pagesize = discussion_list_vault::PAGESIZE_DEFAULT;
 338          }
 339  
 340          return $pagesize;
 341      }
 342  
 343      /**
 344       * Fetch the current page number (zero-indexed).
 345       *
 346       * @param   int         $pageno The zero-indexed page number to use
 347       * @return  int         The normalised page number
 348       */
 349      private function get_page_number(?int $pageno) : int {
 350          if (null === $pageno || $pageno < 0) {
 351              $pageno = 0;
 352          }
 353  
 354          return $pageno;
 355      }
 356  
 357      /**
 358       * Get the list of notification for display.
 359       *
 360       * @param stdClass $user The viewing user
 361       * @param int|null $groupid The forum's group id
 362       * @return      array
 363       */
 364      private function get_notifications(stdClass $user, ?int $groupid): array {
 365          $notifications = $this->notifications;
 366          $forum = $this->forum;
 367          $capabilitymanager = $this->capabilitymanager;
 368  
 369          if ($forum->is_cutoff_date_reached()) {
 370              $notifications[] = (new notification(
 371                      get_string('cutoffdatereached', 'forum'),
 372                      notification::NOTIFY_INFO
 373              ))->set_show_closebutton();
 374          }
 375  
 376          if ($forum->has_blocking_enabled()) {
 377              $notifications[] = (new notification(
 378                  get_string('thisforumisthrottled', 'forum', [
 379                      'blockafter' => $forum->get_block_after(),
 380                      'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
 381                  ]),
 382                  notification::NOTIFY_INFO
 383              ))->set_show_closebutton();
 384          }
 385  
 386          if ($forum->is_in_group_mode() && !$capabilitymanager->can_access_all_groups($user)) {
 387              if ($groupid === null) {
 388                  if (!$capabilitymanager->can_post_to_my_groups($user)) {
 389                      $notifications[] = (new notification(
 390                          get_string('cannotadddiscussiongroup', 'mod_forum'),
 391                          \core\output\notification::NOTIFY_WARNING
 392                      ))->set_show_closebutton();
 393                  } else {
 394                      $notifications[] = (new notification(
 395                          get_string('cannotadddiscussionall', 'mod_forum'),
 396                          \core\output\notification::NOTIFY_WARNING
 397                      ))->set_show_closebutton();
 398                  }
 399              } else if (!$capabilitymanager->can_access_group($user, $groupid)) {
 400                  $notifications[] = (new notification(
 401                      get_string('cannotadddiscussion', 'mod_forum'),
 402                      \core\output\notification::NOTIFY_WARNING
 403                  ))->set_show_closebutton();
 404              }
 405          }
 406  
 407          if ('qanda' === $forum->get_type() && !$capabilitymanager->can_manage_forum($user)) {
 408              $notifications[] = (new notification(
 409                  get_string('qandanotify', 'forum'),
 410                  notification::NOTIFY_INFO
 411              ))->set_show_closebutton()->set_extra_classes(['mt-3']);
 412          }
 413  
 414          if ('eachuser' === $forum->get_type()) {
 415              $notifications[] = (new notification(
 416                  get_string('allowsdiscussions', 'forum'),
 417                  notification::NOTIFY_INFO)
 418              )->set_show_closebutton();
 419          }
 420  
 421          return array_map(function($notification) {
 422              return $notification->export_for_template($this->renderer);
 423          }, $notifications);
 424      }
 425  }