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] [Versions 400 and 401]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Displays a post, and all the posts below it.
  20   * If no post is given, displays all posts in a discussion
  21   *
  22   * @package   mod_forum
  23   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  require_once('../../config.php');
  28  
  29  $d      = required_param('d', PARAM_INT);                // Discussion ID
  30  $parent = optional_param('parent', 0, PARAM_INT);        // If set, then display this post and all children.
  31  $mode   = optional_param('mode', 0, PARAM_INT);          // If set, changes the layout of the thread
  32  $move   = optional_param('move', 0, PARAM_INT);          // If set, moves this discussion to another forum
  33  $mark   = optional_param('mark', '', PARAM_ALPHA);       // Used for tracking read posts if user initiated.
  34  $postid = optional_param('postid', 0, PARAM_INT);        // Used for tracking read posts if user initiated.
  35  $pin    = optional_param('pin', -1, PARAM_INT);          // If set, pin or unpin this discussion.
  36  
  37  $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$d));
  38  if ($parent !== 0) {
  39      $url->param('parent', $parent);
  40  }
  41  $PAGE->set_url($url);
  42  
  43  $vaultfactory = mod_forum\local\container::get_vault_factory();
  44  $discussionvault = $vaultfactory->get_discussion_vault();
  45  $discussion = $discussionvault->get_from_id($d);
  46  
  47  if (!$discussion) {
  48      throw new \moodle_exception('errordiscussionnotfound', 'mod_forum');
  49  }
  50  
  51  $forumvault = $vaultfactory->get_forum_vault();
  52  $forum = $forumvault->get_from_id($discussion->get_forum_id());
  53  
  54  if (!$forum) {
  55      throw new \moodle_exception('errorforumnotfound', 'mod_forum');
  56  }
  57  
  58  $course = $forum->get_course_record();
  59  $cm = $forum->get_course_module_record();
  60  
  61  require_course_login($course, true, $cm);
  62  
  63  $managerfactory = mod_forum\local\container::get_manager_factory();
  64  $capabilitymanager = $managerfactory->get_capability_manager($forum);
  65  $urlfactory = mod_forum\local\container::get_url_factory();
  66  
  67  // Make sure we can render.
  68  if (!$capabilitymanager->can_view_discussions($USER)) {
  69      throw new moodle_exception('noviewdiscussionspermission', 'mod_forum');
  70  }
  71  
  72  $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
  73  $forumdatamapper = $datamapperfactory->get_forum_data_mapper();
  74  $forumrecord = $forumdatamapper->to_legacy_object($forum);
  75  $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
  76  $discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
  77  $discussionviewurl = $urlfactory->get_discussion_view_url_from_discussion($discussion);
  78  // Set the activity record, to avoid additional calls to the db if the page getter is called.
  79  $PAGE->set_activity_record($forumrecord);
  80  
  81  // move this down fix for MDL-6926
  82  require_once($CFG->dirroot . '/mod/forum/lib.php');
  83  
  84  $modcontext = $forum->get_context();
  85  
  86  if (
  87      !empty($CFG->enablerssfeeds) &&
  88      !empty($CFG->forum_enablerssfeeds) &&
  89      $forum->get_rss_type() &&
  90      $forum->get_rss_articles()
  91  ) {
  92      require_once("$CFG->libdir/rsslib.php");
  93  
  94      $rsstitle = format_string(
  95          $course->shortname,
  96          true,
  97          ['context' => context_course::instance($course->id)]
  98      );
  99      $rsstitle .= ': ' . format_string($forum->get_name());
 100      rss_add_http_header($modcontext, 'mod_forum', $forumrecord, $rsstitle);
 101  }
 102  
 103  // Move discussion if requested.
 104  if ($move > 0 && confirm_sesskey()) {
 105      $forumid = $forum->get_id();
 106      $discussionid = $discussion->get_id();
 107      $return = $discussionviewurl->out(false);
 108  
 109      if (!$forumto = $DB->get_record('forum', ['id' => $move])) {
 110          throw new \moodle_exception('cannotmovetonotexist', 'forum', $return);
 111      }
 112  
 113      if (!$capabilitymanager->can_move_discussions($USER)) {
 114          if ($forum->get_type() == 'single') {
 115              throw new \moodle_exception('cannotmovefromsingleforum', 'forum', $return);
 116          } else {
 117              throw new \moodle_exception('nopermissions', 'error', $return, get_capability_string('mod/forum:movediscussions'));
 118          }
 119      }
 120  
 121      if ($forumto->type == 'single') {
 122          throw new \moodle_exception('cannotmovetosingleforum', 'forum', $return);
 123      }
 124  
 125      // Get target forum cm and check it is visible to current user.
 126      $modinfo = get_fast_modinfo($course);
 127      $forums = $modinfo->get_instances_of('forum');
 128      if (!array_key_exists($forumto->id, $forums)) {
 129          throw new \moodle_exception('cannotmovetonotfound', 'forum', $return);
 130      }
 131  
 132      $cmto = $forums[$forumto->id];
 133      if (!$cmto->uservisible) {
 134          throw new \moodle_exception('cannotmovenotvisible', 'forum', $return);
 135      }
 136  
 137      $destinationctx = context_module::instance($cmto->id);
 138      require_capability('mod/forum:startdiscussion', $destinationctx);
 139  
 140      if (!forum_move_attachments($discussionrecord, $forumid, $forumto->id)) {
 141          echo $OUTPUT->notification("Errors occurred while moving attachment directories - check your file permissions");
 142      }
 143      // For each subscribed user in this forum and discussion, copy over per-discussion subscriptions if required.
 144      $discussiongroup = $discussion->get_group_id() == -1 ? 0 : $discussion->get_group_id();
 145      $potentialsubscribers = \mod_forum\subscriptions::fetch_subscribed_users(
 146          $forumrecord,
 147          $discussiongroup,
 148          $modcontext,
 149          'u.id',
 150          true
 151      );
 152  
 153      // Pre-seed the subscribed_discussion caches.
 154      // Firstly for the forum being moved to.
 155      \mod_forum\subscriptions::fill_subscription_cache($forumto->id);
 156      // And also for the discussion being moved.
 157      \mod_forum\subscriptions::fill_subscription_cache($forumid);
 158      $subscriptionchanges = [];
 159      $subscriptiontime = time();
 160      foreach ($potentialsubscribers as $subuser) {
 161          $userid = $subuser->id;
 162          $targetsubscription = \mod_forum\subscriptions::is_subscribed($userid, $forumto, null, $cmto);
 163          $discussionsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord, $discussionid);
 164          $forumsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord);
 165  
 166          if ($forumsubscribed && !$discussionsubscribed && $targetsubscription) {
 167              // The user has opted out of this discussion and the move would cause them to receive notifications again.
 168              // Ensure they are unsubscribed from the discussion still.
 169              $subscriptionchanges[$userid] = \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED;
 170          } else if (!$forumsubscribed && $discussionsubscribed && !$targetsubscription) {
 171              // The user has opted into this discussion and would otherwise not receive the subscription after the move.
 172              // Ensure they are subscribed to the discussion still.
 173              $subscriptionchanges[$userid] = $subscriptiontime;
 174          }
 175      }
 176  
 177      $DB->set_field('forum_discussions', 'forum', $forumto->id, ['id' => $discussionid]);
 178      $DB->set_field('forum_read', 'forumid', $forumto->id, ['discussionid' => $discussionid]);
 179  
 180      // Delete the existing per-discussion subscriptions and replace them with the newly calculated ones.
 181      $DB->delete_records('forum_discussion_subs', ['discussion' => $discussionid]);
 182      $newdiscussion = clone $discussionrecord;
 183      $newdiscussion->forum = $forumto->id;
 184      foreach ($subscriptionchanges as $userid => $preference) {
 185          if ($preference != \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED) {
 186              // Users must have viewdiscussion to a discussion.
 187              if (has_capability('mod/forum:viewdiscussion', $destinationctx, $userid)) {
 188                  \mod_forum\subscriptions::subscribe_user_to_discussion($userid, $newdiscussion, $destinationctx);
 189              }
 190          } else {
 191              \mod_forum\subscriptions::unsubscribe_user_from_discussion($userid, $newdiscussion, $destinationctx);
 192          }
 193      }
 194  
 195      $params = [
 196          'context' => $destinationctx,
 197          'objectid' => $discussionid,
 198          'other' => [
 199              'fromforumid' => $forumid,
 200              'toforumid' => $forumto->id,
 201          ]
 202      ];
 203      $event = \mod_forum\event\discussion_moved::create($params);
 204      $event->add_record_snapshot('forum_discussions', $discussionrecord);
 205      $event->add_record_snapshot('forum', $forumrecord);
 206      $event->add_record_snapshot('forum', $forumto);
 207      $event->trigger();
 208  
 209      // Delete the RSS files for the 2 forums to force regeneration of the feeds
 210      require_once($CFG->dirroot . '/mod/forum/rsslib.php');
 211      forum_rss_delete_file($forumrecord);
 212      forum_rss_delete_file($forumto);
 213  
 214      redirect($return . '&move=-1&sesskey=' . sesskey());
 215  }
 216  // Pin or unpin discussion if requested.
 217  if ($pin !== -1 && confirm_sesskey()) {
 218      if (!$capabilitymanager->can_pin_discussions($USER)) {
 219          throw new \moodle_exception('nopermissions', 'error', $return, get_capability_string('mod/forum:pindiscussions'));
 220      }
 221  
 222      $params = ['context' => $modcontext, 'objectid' => $discussion->get_id(), 'other' => ['forumid' => $forum->get_id()]];
 223  
 224      switch ($pin) {
 225          case FORUM_DISCUSSION_PINNED:
 226              // Pin the discussion and trigger discussion pinned event.
 227              forum_discussion_pin($modcontext, $forumrecord, $discussionrecord);
 228              break;
 229          case FORUM_DISCUSSION_UNPINNED:
 230              // Unpin the discussion and trigger discussion unpinned event.
 231              forum_discussion_unpin($modcontext, $forumrecord, $discussionrecord);
 232              break;
 233          default:
 234              echo $OUTPUT->notification("Invalid value when attempting to pin/unpin discussion");
 235              break;
 236      }
 237  
 238      redirect($discussionviewurl->out(false));
 239  }
 240  
 241  // Trigger discussion viewed event.
 242  forum_discussion_view($modcontext, $forumrecord, $discussionrecord);
 243  
 244  unset($SESSION->fromdiscussion);
 245  
 246  $saveddisplaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
 247  
 248  if ($mode) {
 249      $displaymode = $mode;
 250  } else {
 251      $displaymode = $saveddisplaymode;
 252  }
 253  
 254  if (get_user_preferences('forum_useexperimentalui', false)) {
 255      if ($displaymode == FORUM_MODE_NESTED) {
 256          $displaymode = FORUM_MODE_NESTED_V2;
 257      }
 258  } else {
 259      if ($displaymode == FORUM_MODE_NESTED_V2) {
 260          $displaymode = FORUM_MODE_NESTED;
 261      }
 262  }
 263  
 264  if ($displaymode != $saveddisplaymode) {
 265      set_user_preference('forum_displaymode', $displaymode);
 266  }
 267  
 268  if ($parent) {
 269      // If flat AND parent, then force nested display this time
 270      if ($displaymode == FORUM_MODE_FLATOLDEST or $displaymode == FORUM_MODE_FLATNEWEST) {
 271          $displaymode = FORUM_MODE_NESTED;
 272      }
 273  } else {
 274      $parent = $discussion->get_first_post_id();
 275  }
 276  
 277  $postvault = $vaultfactory->get_post_vault();
 278  if (!$post = $postvault->get_from_id($parent)) {
 279      throw new \moodle_exception("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f={$forum->get_id()}");
 280  }
 281  
 282  if (!$capabilitymanager->can_view_post($USER, $discussion, $post)) {
 283      throw new \moodle_exception('noviewdiscussionspermission', 'forum', "$CFG->wwwroot/mod/forum/view.php?id={$forum->get_id()}");
 284  }
 285  
 286  $istracked = forum_tp_is_tracked($forumrecord, $USER);
 287  if ($mark == 'read'|| $mark == 'unread') {
 288      if ($CFG->forum_usermarksread && forum_tp_can_track_forums($forumrecord) && $istracked) {
 289          if ($mark == 'read') {
 290              forum_tp_add_read_record($USER->id, $postid);
 291          } else {
 292              // unread
 293              forum_tp_delete_read_records($USER->id, $postid);
 294          }
 295      }
 296  }
 297  
 298  $searchform = forum_search_form($course);
 299  
 300  $forumnode = $PAGE->navigation->find($cm->id, navigation_node::TYPE_ACTIVITY);
 301  if (empty($forumnode)) {
 302      $forumnode = $PAGE->navbar;
 303  } else {
 304      $forumnode->make_active();
 305  }
 306  $node = $forumnode->add(format_string($discussion->get_name()), $discussionviewurl);
 307  $node->display = false;
 308  if ($node && $post->get_id() != $discussion->get_first_post_id()) {
 309      $node->add(format_string($post->get_subject()), $PAGE->url);
 310  }
 311  
 312  $isnestedv2displaymode = $displaymode == FORUM_MODE_NESTED_V2;
 313  $PAGE->set_title("$course->shortname: " . format_string($discussion->get_name()));
 314  $PAGE->set_heading($course->fullname);
 315  $PAGE->set_secondary_active_tab('modulepage');
 316  $PAGE->activityheader->disable();
 317  if ($isnestedv2displaymode) {
 318      $PAGE->add_body_class('nested-v2-display-mode reset-style');
 319      $settingstrigger = $OUTPUT->render_from_template('mod_forum/settings_drawer_trigger', null);
 320      $PAGE->add_header_action($settingstrigger);
 321  } else {
 322      $PAGE->add_header_action(forum_search_form($course));
 323  }
 324  
 325  echo $OUTPUT->header();
 326  if (!$isnestedv2displaymode) {
 327      if (!$PAGE->has_secondary_navigation()) {
 328          echo $OUTPUT->heading(format_string($forum->get_name()), 2);
 329      }
 330      echo $OUTPUT->heading(format_string($discussion->get_name()), 3, 'discussionname');
 331  }
 332  
 333  $rendererfactory = mod_forum\local\container::get_renderer_factory();
 334  $discussionrenderer = $rendererfactory->get_discussion_renderer($forum, $discussion, $displaymode);
 335  $orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
 336  $replies = $postvault->get_replies_to_post($USER, $post, $capabilitymanager->can_view_any_private_reply($USER), $orderpostsby);
 337  
 338  if ($move == -1 and confirm_sesskey()) {
 339      $forumname = format_string($forum->get_name(), true);
 340      echo $OUTPUT->notification(get_string('discussionmoved', 'forum', $forumname), 'notifysuccess');
 341  }
 342  
 343  echo $discussionrenderer->render($USER, $post, $replies);
 344  echo $OUTPUT->footer();
 345  
 346  if ($istracked && !$CFG->forum_usermarksread) {
 347      if ($displaymode == FORUM_MODE_THREADED) {
 348          forum_tp_add_read_record($USER->id, $post->get_id());
 349      } else {
 350          $postids = array_map(function($post) {
 351              return $post->get_id();
 352          }, array_merge([$post], array_values($replies)));
 353          forum_tp_mark_posts_read($USER, $postids);
 354      }
 355  }