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