Search moodle.org's
Developer Documentation

See Release Notes

  • 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 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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  
  79  // move this down fix for MDL-6926
  80  require_once($CFG->dirroot . '/mod/forum/lib.php');
  81  
  82  $modcontext = $forum->get_context();
  83  
  84  if (
  85      !empty($CFG->enablerssfeeds) &&
  86      !empty($CFG->forum_enablerssfeeds) &&
  87      $forum->get_rss_type() &&
  88      $forum->get_rss_articles()
  89  ) {
  90      require_once("$CFG->libdir/rsslib.php");
  91  
  92      $rsstitle = format_string(
  93          $course->shortname,
  94          true,
  95          ['context' => context_course::instance($course->id)]
  96      );
  97      $rsstitle .= ': ' . format_string($forum->get_name());
  98      rss_add_http_header($modcontext, 'mod_forum', $forumrecord, $rsstitle);
  99  }
 100  
 101  // Move discussion if requested.
 102  if ($move > 0 && confirm_sesskey()) {
 103      $forumid = $forum->get_id();
 104      $discussionid = $discussion->get_id();
 105      $return = $discussionviewurl->out(false);
 106  
 107      if (!$forumto = $DB->get_record('forum', ['id' => $move])) {
 108          print_error('cannotmovetonotexist', 'forum', $return);
 109      }
 110  
 111      if (!$capabilitymanager->can_move_discussions($USER)) {
 112          if ($forum->get_type() == 'single') {
 113              print_error('cannotmovefromsingleforum', 'forum', $return);
 114          } else {
 115              print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:movediscussions'));
 116          }
 117      }
 118  
 119      if ($forumto->type == 'single') {
 120          print_error('cannotmovetosingleforum', 'forum', $return);
 121      }
 122  
 123      // Get target forum cm and check it is visible to current user.
 124      $modinfo = get_fast_modinfo($course);
 125      $forums = $modinfo->get_instances_of('forum');
 126      if (!array_key_exists($forumto->id, $forums)) {
 127          print_error('cannotmovetonotfound', 'forum', $return);
 128      }
 129  
 130      $cmto = $forums[$forumto->id];
 131      if (!$cmto->uservisible) {
 132          print_error('cannotmovenotvisible', 'forum', $return);
 133      }
 134  
 135      $destinationctx = context_module::instance($cmto->id);
 136      require_capability('mod/forum:startdiscussion', $destinationctx);
 137  
 138      if (!forum_move_attachments($discussionrecord, $forumid, $forumto->id)) {
 139          echo $OUTPUT->notification("Errors occurred while moving attachment directories - check your file permissions");
 140      }
 141      // For each subscribed user in this forum and discussion, copy over per-discussion subscriptions if required.
 142      $discussiongroup = $discussion->get_group_id() == -1 ? 0 : $discussion->get_group_id();
 143      $potentialsubscribers = \mod_forum\subscriptions::fetch_subscribed_users(
 144          $forumrecord,
 145          $discussiongroup,
 146          $modcontext,
 147          'u.id',
 148          true
 149      );
 150  
 151      // Pre-seed the subscribed_discussion caches.
 152      // Firstly for the forum being moved to.
 153      \mod_forum\subscriptions::fill_subscription_cache($forumto->id);
 154      // And also for the discussion being moved.
 155      \mod_forum\subscriptions::fill_subscription_cache($forumid);
 156      $subscriptionchanges = [];
 157      $subscriptiontime = time();
 158      foreach ($potentialsubscribers as $subuser) {
 159          $userid = $subuser->id;
 160          $targetsubscription = \mod_forum\subscriptions::is_subscribed($userid, $forumto, null, $cmto);
 161          $discussionsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord, $discussionid);
 162          $forumsubscribed = \mod_forum\subscriptions::is_subscribed($userid, $forumrecord);
 163  
 164          if ($forumsubscribed && !$discussionsubscribed && $targetsubscription) {
 165              // The user has opted out of this discussion and the move would cause them to receive notifications again.
 166              // Ensure they are unsubscribed from the discussion still.
 167              $subscriptionchanges[$userid] = \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED;
 168          } else if (!$forumsubscribed && $discussionsubscribed && !$targetsubscription) {
 169              // The user has opted into this discussion and would otherwise not receive the subscription after the move.
 170              // Ensure they are subscribed to the discussion still.
 171              $subscriptionchanges[$userid] = $subscriptiontime;
 172          }
 173      }
 174  
 175      $DB->set_field('forum_discussions', 'forum', $forumto->id, ['id' => $discussionid]);
 176      $DB->set_field('forum_read', 'forumid', $forumto->id, ['discussionid' => $discussionid]);
 177  
 178      // Delete the existing per-discussion subscriptions and replace them with the newly calculated ones.
 179      $DB->delete_records('forum_discussion_subs', ['discussion' => $discussionid]);
 180      $newdiscussion = clone $discussionrecord;
 181      $newdiscussion->forum = $forumto->id;
 182      foreach ($subscriptionchanges as $userid => $preference) {
 183          if ($preference != \mod_forum\subscriptions::FORUM_DISCUSSION_UNSUBSCRIBED) {
 184              // Users must have viewdiscussion to a discussion.
 185              if (has_capability('mod/forum:viewdiscussion', $destinationctx, $userid)) {
 186                  \mod_forum\subscriptions::subscribe_user_to_discussion($userid, $newdiscussion, $destinationctx);
 187              }
 188          } else {
 189              \mod_forum\subscriptions::unsubscribe_user_from_discussion($userid, $newdiscussion, $destinationctx);
 190          }
 191      }
 192  
 193      $params = [
 194          'context' => $destinationctx,
 195          'objectid' => $discussionid,
 196          'other' => [
 197              'fromforumid' => $forumid,
 198              'toforumid' => $forumto->id,
 199          ]
 200      ];
 201      $event = \mod_forum\event\discussion_moved::create($params);
 202      $event->add_record_snapshot('forum_discussions', $discussionrecord);
 203      $event->add_record_snapshot('forum', $forumrecord);
 204      $event->add_record_snapshot('forum', $forumto);
 205      $event->trigger();
 206  
 207      // Delete the RSS files for the 2 forums to force regeneration of the feeds
 208      require_once($CFG->dirroot . '/mod/forum/rsslib.php');
 209      forum_rss_delete_file($forumrecord);
 210      forum_rss_delete_file($forumto);
 211  
 212      redirect($return . '&move=-1&sesskey=' . sesskey());
 213  }
 214  // Pin or unpin discussion if requested.
 215  if ($pin !== -1 && confirm_sesskey()) {
 216      if (!$capabilitymanager->can_pin_discussions($USER)) {
 217          print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:pindiscussions'));
 218      }
 219  
 220      $params = ['context' => $modcontext, 'objectid' => $discussion->get_id(), 'other' => ['forumid' => $forum->get_id()]];
 221  
 222      switch ($pin) {
 223          case FORUM_DISCUSSION_PINNED:
 224              // Pin the discussion and trigger discussion pinned event.
 225              forum_discussion_pin($modcontext, $forumrecord, $discussionrecord);
 226              break;
 227          case FORUM_DISCUSSION_UNPINNED:
 228              // Unpin the discussion and trigger discussion unpinned event.
 229              forum_discussion_unpin($modcontext, $forumrecord, $discussionrecord);
 230              break;
 231          default:
 232              echo $OUTPUT->notification("Invalid value when attempting to pin/unpin discussion");
 233              break;
 234      }
 235  
 236      redirect($discussionviewurl->out(false));
 237  }
 238  
 239  // Trigger discussion viewed event.
 240  forum_discussion_view($modcontext, $forumrecord, $discussionrecord);
 241  
 242  unset($SESSION->fromdiscussion);
 243  
 244  $saveddisplaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
 245  
 246  if ($mode) {
 247      $displaymode = $mode;
 248  } else {
 249      $displaymode = $saveddisplaymode;
 250  }
 251  
 252  if (get_user_preferences('forum_useexperimentalui', false)) {
 253      if ($displaymode == FORUM_MODE_NESTED) {
 254          $displaymode = FORUM_MODE_NESTED_V2;
 255      }
 256  } else {
 257      if ($displaymode == FORUM_MODE_NESTED_V2) {
 258          $displaymode = FORUM_MODE_NESTED;
 259      }
 260  }
 261  
 262  if ($displaymode != $saveddisplaymode) {
 263      set_user_preference('forum_displaymode', $displaymode);
 264  }
 265  
 266  if ($parent) {
 267      // If flat AND parent, then force nested display this time
 268      if ($displaymode == FORUM_MODE_FLATOLDEST or $displaymode == FORUM_MODE_FLATNEWEST) {
 269          $displaymode = FORUM_MODE_NESTED;
 270      }
 271  } else {
 272      $parent = $discussion->get_first_post_id();
 273  }
 274  
 275  $postvault = $vaultfactory->get_post_vault();
 276  if (!$post = $postvault->get_from_id($parent)) {
 277      print_error("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f={$forum->get_id()}");
 278  }
 279  
 280  if (!$capabilitymanager->can_view_post($USER, $discussion, $post)) {
 281      print_error('noviewdiscussionspermission', 'forum', "$CFG->wwwroot/mod/forum/view.php?id={$forum->get_id()}");
 282  }
 283  
 284  $istracked = forum_tp_is_tracked($forumrecord, $USER);
 285  if ($mark == 'read'|| $mark == 'unread') {
 286      if ($CFG->forum_usermarksread && forum_tp_can_track_forums($forumrecord) && $istracked) {
 287          if ($mark == 'read') {
 288              forum_tp_add_read_record($USER->id, $postid);
 289          } else {
 290              // unread
 291              forum_tp_delete_read_records($USER->id, $postid);
 292          }
 293      }
 294  }
 295  
 296  $searchform = forum_search_form($course);
 297  
 298  $forumnode = $PAGE->navigation->find($cm->id, navigation_node::TYPE_ACTIVITY);
 299  if (empty($forumnode)) {
 300      $forumnode = $PAGE->navbar;
 301  } else {
 302      $forumnode->make_active();
 303  }
 304  $node = $forumnode->add(format_string($discussion->get_name()), $discussionviewurl);
 305  $node->display = false;
 306  if ($node && $post->get_id() != $discussion->get_first_post_id()) {
 307      $node->add(format_string($post->get_subject()), $PAGE->url);
 308  }
 309  
 310  $isnestedv2displaymode = $displaymode == FORUM_MODE_NESTED_V2;
 311  $PAGE->set_title("$course->shortname: " . format_string($discussion->get_name()));
 312  $PAGE->set_heading($course->fullname);
 313  if ($isnestedv2displaymode) {
 314      $PAGE->add_body_class('nested-v2-display-mode reset-style');
 315      $settingstrigger = $OUTPUT->render_from_template('mod_forum/settings_drawer_trigger', null);
 316      $PAGE->add_header_action($settingstrigger);
 317  } else {
 318      $PAGE->set_button(forum_search_form($course));
 319  }
 320  
 321  echo $OUTPUT->header();
 322  if (!$isnestedv2displaymode) {
 323      echo $OUTPUT->heading(format_string($forum->get_name()), 2);
 324      echo $OUTPUT->heading(format_string($discussion->get_name()), 3, 'discussionname');
 325  }
 326  
 327  $rendererfactory = mod_forum\local\container::get_renderer_factory();
 328  $discussionrenderer = $rendererfactory->get_discussion_renderer($forum, $discussion, $displaymode);
 329  $orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
 330  $replies = $postvault->get_replies_to_post($USER, $post, $capabilitymanager->can_view_any_private_reply($USER), $orderpostsby);
 331  
 332  if ($move == -1 and confirm_sesskey()) {
 333      $forumname = format_string($forum->get_name(), true);
 334      echo $OUTPUT->notification(get_string('discussionmoved', 'forum', $forumname), 'notifysuccess');
 335  }
 336  
 337  echo $discussionrenderer->render($USER, $post, $replies);
 338  echo $OUTPUT->footer();
 339  
 340  if ($istracked && !$CFG->forum_usermarksread) {
 341      if ($displaymode == FORUM_MODE_THREADED) {
 342          forum_tp_add_read_record($USER->id, $post->get_id());
 343      } else {
 344          $postids = array_map(function($post) {
 345              return $post->get_id();
 346          }, array_merge([$post], array_values($replies)));
 347          forum_tp_mark_posts_read($USER, $postids);
 348      }
 349  }