MDL-73233 frontpage: Display link to My courses
[moodle.git] / mod / forum / discuss.php
blobe06742cd6afd7c4cc42a2cf8829686103a3bca38
1 <?php
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.
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/>.
18 /**
19 * Displays a post, and all the posts below it.
20 * If no post is given, displays all posts in a discussion
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
27 require_once('../../config.php');
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.
37 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$d));
38 if ($parent !== 0) {
39 $url->param('parent', $parent);
41 $PAGE->set_url($url);
43 $vaultfactory = mod_forum\local\container::get_vault_factory();
44 $discussionvault = $vaultfactory->get_discussion_vault();
45 $discussion = $discussionvault->get_from_id($d);
47 if (!$discussion) {
48 throw new \moodle_exception('Unable to find discussion with id ' . $discussionid);
51 $forumvault = $vaultfactory->get_forum_vault();
52 $forum = $forumvault->get_from_id($discussion->get_forum_id());
54 if (!$forum) {
55 throw new \moodle_exception('Unable to find forum with id ' . $discussion->get_forum_id());
58 $course = $forum->get_course_record();
59 $cm = $forum->get_course_module_record();
61 require_course_login($course, true, $cm);
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();
67 // Make sure we can render.
68 if (!$capabilitymanager->can_view_discussions($USER)) {
69 throw new moodle_exception('noviewdiscussionspermission', 'mod_forum');
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);
81 // move this down fix for MDL-6926
82 require_once($CFG->dirroot . '/mod/forum/lib.php');
84 $modcontext = $forum->get_context();
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");
94 $rsstitle = format_string(
95 $course->shortname,
96 true,
97 ['context' => context_course::instance($course->id)]
99 $rsstitle .= ': ' . format_string($forum->get_name());
100 rss_add_http_header($modcontext, 'mod_forum', $forumrecord, $rsstitle);
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);
109 if (!$forumto = $DB->get_record('forum', ['id' => $move])) {
110 print_error('cannotmovetonotexist', 'forum', $return);
113 if (!$capabilitymanager->can_move_discussions($USER)) {
114 if ($forum->get_type() == 'single') {
115 print_error('cannotmovefromsingleforum', 'forum', $return);
116 } else {
117 print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:movediscussions'));
121 if ($forumto->type == 'single') {
122 print_error('cannotmovetosingleforum', 'forum', $return);
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 print_error('cannotmovetonotfound', 'forum', $return);
132 $cmto = $forums[$forumto->id];
133 if (!$cmto->uservisible) {
134 print_error('cannotmovenotvisible', 'forum', $return);
137 $destinationctx = context_module::instance($cmto->id);
138 require_capability('mod/forum:startdiscussion', $destinationctx);
140 if (!forum_move_attachments($discussionrecord, $forumid, $forumto->id)) {
141 echo $OUTPUT->notification("Errors occurred while moving attachment directories - check your file permissions");
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
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);
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;
177 $DB->set_field('forum_discussions', 'forum', $forumto->id, ['id' => $discussionid]);
178 $DB->set_field('forum_read', 'forumid', $forumto->id, ['discussionid' => $discussionid]);
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);
190 } else {
191 \mod_forum\subscriptions::unsubscribe_user_from_discussion($userid, $newdiscussion, $destinationctx);
195 $params = [
196 'context' => $destinationctx,
197 'objectid' => $discussionid,
198 'other' => [
199 'fromforumid' => $forumid,
200 'toforumid' => $forumto->id,
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();
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);
214 redirect($return . '&move=-1&sesskey=' . sesskey());
216 // Pin or unpin discussion if requested.
217 if ($pin !== -1 && confirm_sesskey()) {
218 if (!$capabilitymanager->can_pin_discussions($USER)) {
219 print_error('nopermissions', 'error', $return, get_capability_string('mod/forum:pindiscussions'));
222 $params = ['context' => $modcontext, 'objectid' => $discussion->get_id(), 'other' => ['forumid' => $forum->get_id()]];
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;
238 redirect($discussionviewurl->out(false));
241 // Trigger discussion viewed event.
242 forum_discussion_view($modcontext, $forumrecord, $discussionrecord);
244 unset($SESSION->fromdiscussion);
246 $saveddisplaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
248 if ($mode) {
249 $displaymode = $mode;
250 } else {
251 $displaymode = $saveddisplaymode;
254 if (get_user_preferences('forum_useexperimentalui', false)) {
255 if ($displaymode == FORUM_MODE_NESTED) {
256 $displaymode = FORUM_MODE_NESTED_V2;
258 } else {
259 if ($displaymode == FORUM_MODE_NESTED_V2) {
260 $displaymode = FORUM_MODE_NESTED;
264 if ($displaymode != $saveddisplaymode) {
265 set_user_preference('forum_displaymode', $displaymode);
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;
273 } else {
274 $parent = $discussion->get_first_post_id();
277 $postvault = $vaultfactory->get_post_vault();
278 if (!$post = $postvault->get_from_id($parent)) {
279 print_error("notexists", 'forum', "$CFG->wwwroot/mod/forum/view.php?f={$forum->get_id()}");
282 if (!$capabilitymanager->can_view_post($USER, $discussion, $post)) {
283 print_error('noviewdiscussionspermission', 'forum', "$CFG->wwwroot/mod/forum/view.php?id={$forum->get_id()}");
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);
298 $searchform = forum_search_form($course);
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();
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);
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));
325 echo $OUTPUT->header();
326 if (!$isnestedv2displaymode) {
327 if (!$PAGE->has_secondary_navigation()) {
328 echo $OUTPUT->heading(format_string($forum->get_name()), 2);
330 echo $OUTPUT->heading(format_string($discussion->get_name()), 3, 'discussionname');
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);
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');
343 echo $discussionrenderer->render($USER, $post, $replies);
344 echo $OUTPUT->footer();
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);