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 27require_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)); 38if ($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 47if (!$discussion) { 48 throw new \moodle_exception('Unable to find discussion with id ' . $discussionid); 49} 50 51$forumvault = $vaultfactory->get_forum_vault(); 52$forum = $forumvault->get_from_id($discussion->get_forum_id()); 53 54if (!$forum) { 55 throw new \moodle_exception('Unable to find forum with id ' . $discussion->get_forum_id()); 56} 57 58$course = $forum->get_course_record(); 59$cm = $forum->get_course_module_record(); 60 61require_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. 68if (!$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 80require_once($CFG->dirroot . '/mod/forum/lib.php'); 81 82$modcontext = $forum->get_context(); 83 84if ( 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. 102if ($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. 215if ($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. 240forum_discussion_view($modcontext, $forumrecord, $discussionrecord); 241 242unset($SESSION->fromdiscussion); 243 244$saveddisplaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode); 245 246if ($mode) { 247 $displaymode = $mode; 248} else { 249 $displaymode = $saveddisplaymode; 250} 251 252if (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 262if ($displaymode != $saveddisplaymode) { 263 set_user_preference('forum_displaymode', $displaymode); 264} 265 266if ($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(); 276if (!$post = $postvault->get_from_id($parent)) { 277 print_error("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f={$forum->get_id()}"); 278} 279 280if (!$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); 285if ($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); 299if (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; 306if ($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); 313if ($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 321echo $OUTPUT->header(); 322if (!$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 332if ($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 337echo $discussionrenderer->render($USER, $post, $replies); 338echo $OUTPUT->footer(); 339 340if ($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} 350