MDL-65069 mod_forum: moodle_url to ensure we call the correct post.php
[moodle.git] / mod / forum / classes / local / renderers / discussion_list.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Discussion list renderer.
19  *
20  * @package    mod_forum
21  * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace mod_forum\local\renderers;
27 defined('MOODLE_INTERNAL') || die();
29 use mod_forum\local\entities\forum as forum_entity;
30 use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
31 use mod_forum\local\factories\exporter as exporter_factory;
32 use mod_forum\local\factories\vault as vault_factory;
33 use mod_forum\local\factories\url as url_factory;
34 use mod_forum\local\managers\capability as capability_manager;
35 use mod_forum\local\vaults\discussion_list as discussion_list_vault;
36 use renderer_base;
37 use stdClass;
38 use core\output\notification;
39 use mod_forum\local\factories\builder as builder_factory;
41 require_once($CFG->dirroot . '/mod/forum/lib.php');
43 /**
44  * The discussion list renderer.
45  *
46  * @package    mod_forum
47  * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
48  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49  */
50 class discussion_list {
51     /** @var forum_entity The forum being rendered */
52     private $forum;
54     /** @var stdClass The DB record for the forum being rendered */
55     private $forumrecord;
57     /** @var renderer_base The renderer used to render the view */
58     private $renderer;
60     /** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
61     private $legacydatamapperfactory;
63     /** @var exporter_factory $exporterfactory Exporter factory */
64     private $exporterfactory;
66     /** @var vault_factory $vaultfactory Vault factory */
67     private $vaultfactory;
69     /** @var capability_manager $capabilitymanager Capability manager */
70     private $capabilitymanager;
72     /** @var url_factory $urlfactory URL factory */
73     private $urlfactory;
75     /** @var array $notifications List of notification HTML */
76     private $notifications;
78     /** @var builder_factory $builderfactory Builder factory */
79     private $builderfactory;
81     /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
82     private $postprocessfortemplate;
84     /** @var string $template The template to use when displaying */
85     private $template;
87     /**
88      * Constructor for a new discussion list renderer.
89      *
90      * @param   forum_entity        $forum The forum entity to be rendered
91      * @param   renderer_base       $renderer The renderer used to render the view
92      * @param   legacy_data_mapper_factory $legacydatamapperfactory The factory used to fetch a legacy record
93      * @param   exporter_factory    $exporterfactory The factory used to fetch exporter instances
94      * @param   vault_factory       $vaultfactory The factory used to fetch the vault instances
95      * @param   builder_factory     $builderfactory The factory used to fetch the builder instances
96      * @param   capability_manager  $capabilitymanager The managed used to check capabilities on the forum
97      * @param   url_factory         $urlfactory The factory used to create URLs in the forum
98      * @param   string              $template
99      * @param   notification[]      $notifications A list of any notifications to be displayed within the page
100      * @param   callable|null       $postprocessfortemplate Callback function to process discussion lists for templates
101      */
102     public function __construct(
103         forum_entity $forum,
104         renderer_base $renderer,
105         legacy_data_mapper_factory $legacydatamapperfactory,
106         exporter_factory $exporterfactory,
107         vault_factory $vaultfactory,
108         builder_factory $builderfactory,
109         capability_manager $capabilitymanager,
110         url_factory $urlfactory,
111         string $template,
112         array $notifications = [],
113         callable $postprocessfortemplate = null
114     ) {
115         $this->forum = $forum;
116         $this->renderer = $renderer;
117         $this->legacydatamapperfactory = $legacydatamapperfactory;
118         $this->exporterfactory = $exporterfactory;
119         $this->vaultfactory = $vaultfactory;
120         $this->builderfactory = $builderfactory;
121         $this->capabilitymanager = $capabilitymanager;
123         $this->urlfactory = $urlfactory;
124         $this->notifications = $notifications;
125         $this->postprocessfortemplate = $postprocessfortemplate;
126         $this->template = $template;
128         $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
129         $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
130     }
132     /**
133      * Render for the specified user.
134      *
135      * @param   stdClass    $user The user to render for
136      * @param   cm_info     $cm The course module info for this discussion list
137      * @param   int         $groupid The group to render
138      * @param   int         $sortorder The sort order to use when selecting the discussions in the list
139      * @param   int         $pageno The zero-indexed page number to use
140      * @param   int         $pagesize The number of discussions to show on the page
141      * @return  string      The rendered content for display
142      */
143     public function render(stdClass $user, \cm_info $cm, ?int $groupid, ?int $sortorder, ?int $pageno, ?int $pagesize) : string {
144         global $PAGE;
146         $forum = $this->forum;
148         $pagesize = $this->get_page_size($pagesize);
149         $pageno = $this->get_page_number($pageno);
151         $groupids = $this->get_groups_from_groupid($user, $groupid);
152         $forumexporter = $this->exporterfactory->get_forum_exporter(
153             $user,
154             $this->forum,
155             $groupid
156         );
158         // Count all forum discussion posts.
159         $alldiscussionscount = $this->get_count_all_discussions($user, $groupids);
161         // Get all forum discussions posts.
162         $discussions = $this->get_discussions($user, $groupids, $sortorder, $pageno, $pagesize);
164         $forumview = [
165             'forum' => (array) $forumexporter->export($this->renderer),
166             'newdiscussionhtml' => $this->get_discussion_form($user, $cm, $groupid),
167             'groupchangemenu' => groups_print_activity_menu(
168                 $cm,
169                 $this->urlfactory->get_forum_view_url_from_forum($forum),
170                 true
171             ),
172             'hasmore' => ($alldiscussionscount > $pagesize),
173             'notifications' => $this->get_notifications($user, $groupid),
174         ];
176         if (!$discussions) {
177             return $this->renderer->render_from_template($this->template, $forumview);
178         }
180         if ($this->postprocessfortemplate !== null) {
181             // We've got some post processing to do!
182             $exportedposts = ($this->postprocessfortemplate) ($discussions, $user, $forum);
183         }
185         $forumview = array_merge(
186             $forumview,
187             [
188                 'pagination' => $this->renderer->render(new \paging_bar($alldiscussionscount, $pageno, $pagesize, $PAGE->url, 'p')),
189             ],
190             $exportedposts
191         );
193         return $this->renderer->render_from_template($this->template, $forumview);
194     }
196     /**
197      * Get the mod_forum_post_form. This is the default boiler plate from mod_forum/post_form.php with the inpage flag caveat
198      *
199      * @param stdClass $user The user the form is being generated for
200      * @param \cm_info $cm
201      * @param int $groupid The groupid if any
202      *
203      * @return string The rendered html
204      */
205     private function get_discussion_form(stdClass $user, \cm_info $cm, ?int $groupid) {
206         $forum = $this->forum;
207         $forumrecord = $this->legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
208         $modcontext = \context_module::instance($cm->id);
209         $coursecontext = \context_course::instance($forum->get_course_id());
210         $post = (object) [
211             'course' => $forum->get_course_id(),
212             'forum' => $forum->get_id(),
213             'discussion' => 0,           // Ie discussion # not defined yet.
214             'parent' => 0,
215             'subject' => '',
216             'userid' => $user->id,
217             'message' => '',
218             'messageformat' => editors_get_preferred_format(),
219             'messagetrust' => 0,
220             'groupid' => $groupid,
221         ];
222         $thresholdwarning = forum_check_throttling($forumrecord, $cm);
224         $formparams = array(
225             'course' => $forum->get_course_record(),
226             'cm' => $cm,
227             'coursecontext' => $coursecontext,
228             'modcontext' => $modcontext,
229             'forum' => $forumrecord,
230             'post' => $post,
231             'subscribe' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord,
232                 null, $cm),
233             'thresholdwarning' => $thresholdwarning,
234             'inpagereply' => true,
235             'edit' => 0
236         );
237         $posturl = new \moodle_url('/mod/forum/post.php');
238         $mformpost = new \mod_forum_post_form($posturl, $formparams, 'post', '', array('id' => 'mformforum'));
239         $discussionsubscribe = \mod_forum\subscriptions::get_user_default_subscription($forumrecord, $coursecontext, $cm, null);
241         $params = array('reply' => 0, 'forum' => $forumrecord->id, 'edit' => 0) +
242             (isset($post->groupid) ? array('groupid' => $post->groupid) : array()) +
243             array(
244                 'userid' => $post->userid,
245                 'parent' => $post->parent,
246                 'discussion' => $post->discussion,
247                 'course' => $forum->get_course_id(),
248                 'discussionsubscribe' => $discussionsubscribe
249             );
250         $mformpost->set_data($params);
252         return $mformpost->render();
253     }
255     /**
256      * Get the list of groups to show based on the current user and requested groupid.
257      *
258      * @param   stdClass    $user The user viewing
259      * @param   int         $groupid The groupid requested
260      * @return  array       The list of groups to show
261      */
262     private function get_groups_from_groupid(stdClass $user, ?int $groupid) : ?array {
263         $forum = $this->forum;
264         $effectivegroupmode = $forum->get_effective_group_mode();
265         if (empty($effectivegroupmode)) {
266             // This forum is not in a group mode. Show all posts always.
267             return null;
268         }
270         if (null == $groupid) {
271             // No group was specified.
272             $showallgroups = (VISIBLEGROUPS == $effectivegroupmode);
273             $showallgroups = $showallgroups || $this->capabilitymanager->can_access_all_groups($user);
274             if ($showallgroups) {
275                 // Return null to show all groups.
276                 return null;
277             } else {
278                 // No group was specified. Only show the users current groups.
279                 return array_keys(
280                     groups_get_all_groups(
281                         $forum->get_course_id(),
282                         $user->id,
283                         $forum->get_course_module_record()->groupingid
284                     )
285                 );
286             }
287         } else {
288             // A group was specified. Just show that group.
289             return [$groupid];
290         }
291     }
293     /**
294      * Fetch the data used to display the discussions on the current page.
295      *
296      * @param   stdClass    $user The user to render for
297      * @param   int[]|null  $groupids The group ids for this list of discussions
298      * @param   int|null    $sortorder The sort order to use when selecting the discussions in the list
299      * @param   int|null    $pageno The zero-indexed page number to use
300      * @param   int|null    $pagesize The number of discussions to show on the page
301      * @return  stdClass    The data to use for display
302      */
303     private function get_discussions(stdClass $user, ?array $groupids, ?int $sortorder, ?int $pageno, ?int $pagesize) {
304         $forum = $this->forum;
305         $discussionvault = $this->vaultfactory->get_discussions_in_forum_vault();
306         if (null === $groupids) {
307             return $discussions = $discussionvault->get_from_forum_id(
308                 $forum->get_id(),
309                 $this->capabilitymanager->can_view_hidden_posts($user),
310                 $user->id,
311                 $sortorder,
312                 $this->get_page_size($pagesize),
313                 $this->get_page_number($pageno) * $this->get_page_size($pagesize));
314         } else {
315             return $discussions = $discussionvault->get_from_forum_id_and_group_id(
316                 $forum->get_id(),
317                 $groupids,
318                 $this->capabilitymanager->can_view_hidden_posts($user),
319                 $user->id,
320                 $sortorder,
321                 $this->get_page_size($pagesize),
322                 $this->get_page_number($pageno) * $this->get_page_size($pagesize));
323         }
324     }
326     /**
327      * Get a count of all discussions in a forum.
328      *
329      * @param   stdClass    $user The user to render for
330      * @param   array       $groupids The array of groups to render
331      * @return  int         The number of discussions in a forum
332      */
333     public function get_count_all_discussions(stdClass $user, ?array $groupids) {
334         $discussionvault = $this->vaultfactory->get_discussions_in_forum_vault();
335         if (null === $groupids) {
336             return $discussionvault->get_total_discussion_count_from_forum_id(
337                 $this->forum->get_id(),
338                 $this->capabilitymanager->can_view_hidden_posts($user),
339                 $user->id);
340         } else {
341             return $discussionvault->get_total_discussion_count_from_forum_id_and_group_id(
342                 $this->forum->get_id(),
343                 $groupids,
344                 $this->capabilitymanager->can_view_hidden_posts($user),
345                 $user->id);
346         }
347     }
349     /**
350      * Fetch the page size to use when displaying the page.
351      *
352      * @param   int         $pagesize The number of discussions to show on the page
353      * @return  int         The normalised page size
354      */
355     private function get_page_size(?int $pagesize) : int {
356         if (null === $pagesize || $pagesize <= 0) {
357             $pagesize = discussion_list_vault::PAGESIZE_DEFAULT;
358         }
360         return $pagesize;
361     }
363     /**
364      * Fetch the current page number (zero-indexed).
365      *
366      * @param   int         $pageno The zero-indexed page number to use
367      * @return  int         The normalised page number
368      */
369     private function get_page_number(?int $pageno) : int {
370         if (null === $pageno || $pageno < 0) {
371             $pageno = 0;
372         }
374         return $pageno;
375     }
377     /**
378      * Get the list of notification for display.
379      *
380      * @param stdClass $user The viewing user
381      * @param int|null $groupid The forum's group id
382      * @return      array
383      */
384     private function get_notifications(stdClass $user, ?int $groupid) : array {
385         $notifications = $this->notifications;
386         $forum = $this->forum;
387         $renderer = $this->renderer;
388         $capabilitymanager = $this->capabilitymanager;
390         if ($forum->is_cutoff_date_reached()) {
391             $notifications[] = (new notification(
392                     get_string('cutoffdatereached', 'forum'),
393                     notification::NOTIFY_INFO
394             ))->set_show_closebutton();
395         } else if ($forum->is_due_date_reached()) {
396             $notifications[] = (new notification(
397                     get_string('thisforumisdue', 'forum', userdate($forum->get_due_date())),
398                     notification::NOTIFY_INFO
399             ))->set_show_closebutton();
400         } else if ($forum->has_due_date()) {
401             $notifications[] = (new notification(
402                     get_string('thisforumhasduedate', 'forum', userdate($forum->get_due_date())),
403                     notification::NOTIFY_INFO
404             ))->set_show_closebutton();
405         }
407         if ($forum->has_blocking_enabled()) {
408             $notifications[] = (new notification(
409                 get_string('thisforumisthrottled', 'forum', [
410                     'blockafter' => $forum->get_block_after(),
411                     'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
412                 ])
413             ))->set_show_closebutton();
414         }
416         if ($forum->is_in_group_mode() && !$capabilitymanager->can_access_all_groups($user)) {
417             if ($groupid === null) {
418                 if (!$capabilitymanager->can_post_to_my_groups($user)) {
419                     $notifications[] = (new notification(
420                         get_string('cannotadddiscussiongroup', 'mod_forum'),
421                         \core\output\notification::NOTIFY_WARNING
422                     ))->set_show_closebutton();
423                 } else {
424                     $notifications[] = (new notification(
425                         get_string('cannotadddiscussionall', 'mod_forum'),
426                         \core\output\notification::NOTIFY_WARNING
427                     ))->set_show_closebutton();
428                 }
429             } else if (!$capabilitymanager->can_access_group($user, $groupid)) {
430                 $notifications[] = (new notification(
431                     get_string('cannotadddiscussion', 'mod_forum'),
432                     \core\output\notification::NOTIFY_WARNING
433                 ))->set_show_closebutton();
434             }
435         }
437         if ('qanda' === $forum->get_type() && !$capabilitymanager->can_manage_forum($user)) {
438             $notifications[] = (new notification(
439                 get_string('qandanotify', 'forum'),
440                 notification::NOTIFY_INFO
441             ))->set_show_closebutton();
442         }
444         if ('eachuser' === $forum->get_type()) {
445             $notifications[] = (new notification(
446                 get_string('allowsdiscussions', 'forum'),
447                 notification::NOTIFY_INFO)
448             )->set_show_closebutton();
449         }
451         return array_map(function($notification) {
452             return $notification->export_for_template($this->renderer);
453         }, $notifications);
454     }