96a07256ca81b2cfad2764a8aab2b1b84813ca56
[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\grades\forum_gradeitem;
30 use mod_forum\local\entities\forum as forum_entity;
31 use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
32 use mod_forum\local\factories\exporter as exporter_factory;
33 use mod_forum\local\factories\vault as vault_factory;
34 use mod_forum\local\factories\url as url_factory;
35 use mod_forum\local\managers\capability as capability_manager;
36 use mod_forum\local\vaults\discussion_list as discussion_list_vault;
37 use renderer_base;
38 use stdClass;
39 use core\output\notification;
40 use mod_forum\local\factories\builder as builder_factory;
42 require_once($CFG->dirroot . '/mod/forum/lib.php');
44 /**
45  * The discussion list renderer.
46  *
47  * @package    mod_forum
48  * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
49  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50  */
51 class discussion_list {
52     /** @var forum_entity The forum being rendered */
53     private $forum;
55     /** @var stdClass The DB record for the forum being rendered */
56     private $forumrecord;
58     /** @var renderer_base The renderer used to render the view */
59     private $renderer;
61     /** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
62     private $legacydatamapperfactory;
64     /** @var exporter_factory $exporterfactory Exporter factory */
65     private $exporterfactory;
67     /** @var vault_factory $vaultfactory Vault factory */
68     private $vaultfactory;
70     /** @var capability_manager $capabilitymanager Capability manager */
71     private $capabilitymanager;
73     /** @var url_factory $urlfactory URL factory */
74     private $urlfactory;
76     /** @var array $notifications List of notification HTML */
77     private $notifications;
79     /** @var builder_factory $builderfactory Builder factory */
80     private $builderfactory;
82     /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
83     private $postprocessfortemplate;
85     /** @var string $template The template to use when displaying */
86     private $template;
88     /** @var gradeitem The gradeitem instance associated with this forum */
89     private $forumgradeitem;
91     /**
92      * Constructor for a new discussion list renderer.
93      *
94      * @param   forum_entity        $forum The forum entity to be rendered
95      * @param   renderer_base       $renderer The renderer used to render the view
96      * @param   legacy_data_mapper_factory $legacydatamapperfactory The factory used to fetch a legacy record
97      * @param   exporter_factory    $exporterfactory The factory used to fetch exporter instances
98      * @param   vault_factory       $vaultfactory The factory used to fetch the vault instances
99      * @param   builder_factory     $builderfactory The factory used to fetch the builder instances
100      * @param   capability_manager  $capabilitymanager The managed used to check capabilities on the forum
101      * @param   url_factory         $urlfactory The factory used to create URLs in the forum
102      * @param   string              $template
103      * @param   notification[]      $notifications A list of any notifications to be displayed within the page
104      * @param   callable|null       $postprocessfortemplate Callback function to process discussion lists for templates
105      */
106     public function __construct(
107         forum_entity $forum,
108         renderer_base $renderer,
109         legacy_data_mapper_factory $legacydatamapperfactory,
110         exporter_factory $exporterfactory,
111         vault_factory $vaultfactory,
112         builder_factory $builderfactory,
113         capability_manager $capabilitymanager,
114         url_factory $urlfactory,
115         forum_gradeitem $forumgradeitem,
116         string $template,
117         array $notifications = [],
118         callable $postprocessfortemplate = null
119     ) {
120         $this->forum = $forum;
121         $this->renderer = $renderer;
122         $this->legacydatamapperfactory = $legacydatamapperfactory;
123         $this->exporterfactory = $exporterfactory;
124         $this->vaultfactory = $vaultfactory;
125         $this->builderfactory = $builderfactory;
126         $this->capabilitymanager = $capabilitymanager;
128         $this->urlfactory = $urlfactory;
129         $this->notifications = $notifications;
130         $this->postprocessfortemplate = $postprocessfortemplate;
131         $this->template = $template;
132         $this->forumgradeitem = $forumgradeitem;
134         $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
135         $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
136     }
138     /**
139      * Render for the specified user.
140      *
141      * @param   stdClass    $user The user to render for
142      * @param   cm_info     $cm The course module info for this discussion list
143      * @param   int         $groupid The group to render
144      * @param   int         $sortorder The sort order to use when selecting the discussions in the list
145      * @param   int         $pageno The zero-indexed page number to use
146      * @param   int         $pagesize The number of discussions to show on the page
147      * @param   int         $displaymode The discussion display mode
148      * @return  string      The rendered content for display
149      */
150     public function render(
151         stdClass $user,
152         \cm_info $cm,
153         ?int $groupid,
154         ?int $sortorder,
155         ?int $pageno,
156         ?int $pagesize,
157         int $displaymode = null
158     ) : string {
159         global $PAGE;
161         $forum = $this->forum;
162         $course = $forum->get_course_record();
164         $forumexporter = $this->exporterfactory->get_forum_exporter(
165             $user,
166             $this->forum,
167             $groupid
168         );
170         $pagesize = $this->get_page_size($pagesize);
171         $pageno = $this->get_page_number($pageno);
173         // Count all forum discussion posts.
174         $alldiscussionscount = mod_forum_count_all_discussions($forum, $user, $groupid);
176         // Get all forum discussion summaries.
177         $discussions = mod_forum_get_discussion_summaries($forum, $user, $groupid, $sortorder, $pageno, $pagesize);
179         $capabilitymanager = $this->capabilitymanager;
180         $hasanyactions = false;
181         $hasanyactions = $hasanyactions || $capabilitymanager->can_favourite_discussion($user);
182         $hasanyactions = $hasanyactions || $capabilitymanager->can_pin_discussions($user);
183         $hasanyactions = $hasanyactions || $capabilitymanager->can_manage_forum($user);
185         $forumview = [
186             'forum' => (array) $forumexporter->export($this->renderer),
187             'contextid' => $forum->get_context()->id,
188             'cmid' => $cm->id,
189             'name' => $forum->get_name(),
190             'courseid' => $course->id,
191             'coursename' => $course->shortname,
192             'experimentaldisplaymode' => $displaymode == FORUM_MODE_NESTED_V2,
193             'gradingcomponent' => $this->forumgradeitem->get_grading_component_name(),
194             'gradingcomponentsubtype' => $this->forumgradeitem->get_grading_component_subtype(),
195             'sendstudentnotifications' => $forum->should_notify_students_default_when_grade_for_forum(),
196             'hasanyactions' => $hasanyactions,
197             'groupchangemenu' => groups_print_activity_menu(
198                 $cm,
199                 $this->urlfactory->get_forum_view_url_from_forum($forum),
200                 true
201             ),
202             'hasmore' => ($alldiscussionscount > $pagesize),
203             'notifications' => $this->get_notifications($user, $groupid),
204             'settings' => [
205                 'excludetext' => true,
206                 'togglemoreicon' => true,
207                 'excludesubscription' => true
208             ],
209             'totaldiscussioncount' => $alldiscussionscount,
210             'userid' => $user->id,
211             'visiblediscussioncount' => count($discussions)
212         ];
214         if ($forumview['forum']['capabilities']['create']) {
215             $forumview['newdiscussionhtml'] = $this->get_discussion_form($user, $cm, $groupid);
216         }
218         if (!$discussions) {
219             return $this->renderer->render_from_template($this->template, $forumview);
220         }
222         if ($this->postprocessfortemplate !== null) {
223             // We've got some post processing to do!
224             $exportedposts = ($this->postprocessfortemplate) ($discussions, $user, $forum);
225         }
227         $baseurl = new \moodle_url($PAGE->url, array('o' => $sortorder));
229         $forumview = array_merge(
230             $forumview,
231             [
232                 'pagination' => $this->renderer->render(new \paging_bar($alldiscussionscount, $pageno, $pagesize, $baseurl, 'p')),
233             ],
234             $exportedposts
235         );
237         $firstdiscussion = reset($discussions);
238         $forumview['firstgradeduserid'] = $firstdiscussion->get_latest_post_author()->get_id();
240         return $this->renderer->render_from_template($this->template, $forumview);
241     }
243     /**
244      * Get the mod_forum_post_form. This is the default boiler plate from mod_forum/post_form.php with the inpage flag caveat
245      *
246      * @param stdClass $user The user the form is being generated for
247      * @param \cm_info $cm
248      * @param int $groupid The groupid if any
249      *
250      * @return string The rendered html
251      */
252     private function get_discussion_form(stdClass $user, \cm_info $cm, ?int $groupid) {
253         $forum = $this->forum;
254         $forumrecord = $this->legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
255         $modcontext = \context_module::instance($cm->id);
256         $coursecontext = \context_course::instance($forum->get_course_id());
257         $post = (object) [
258             'course' => $forum->get_course_id(),
259             'forum' => $forum->get_id(),
260             'discussion' => 0,           // Ie discussion # not defined yet.
261             'parent' => 0,
262             'subject' => '',
263             'userid' => $user->id,
264             'message' => '',
265             'messageformat' => editors_get_preferred_format(),
266             'messagetrust' => 0,
267             'groupid' => $groupid,
268         ];
269         $thresholdwarning = forum_check_throttling($forumrecord, $cm);
271         $formparams = array(
272             'course' => $forum->get_course_record(),
273             'cm' => $cm,
274             'coursecontext' => $coursecontext,
275             'modcontext' => $modcontext,
276             'forum' => $forumrecord,
277             'post' => $post,
278             'subscribe' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord,
279                 null, $cm),
280             'thresholdwarning' => $thresholdwarning,
281             'inpagereply' => true,
282             'edit' => 0
283         );
284         $posturl = new \moodle_url('/mod/forum/post.php');
285         $mformpost = new \mod_forum_post_form($posturl, $formparams, 'post', '', array('id' => 'mformforum'));
286         $discussionsubscribe = \mod_forum\subscriptions::get_user_default_subscription($forumrecord, $coursecontext, $cm, null);
288         $params = array('reply' => 0, 'forum' => $forumrecord->id, 'edit' => 0) +
289             (isset($post->groupid) ? array('groupid' => $post->groupid) : array()) +
290             array(
291                 'userid' => $post->userid,
292                 'parent' => $post->parent,
293                 'discussion' => $post->discussion,
294                 'course' => $forum->get_course_id(),
295                 'discussionsubscribe' => $discussionsubscribe
296             );
297         $mformpost->set_data($params);
299         return $mformpost->render();
300     }
302     /**
303      * Fetch the page size to use when displaying the page.
304      *
305      * @param   int         $pagesize The number of discussions to show on the page
306      * @return  int         The normalised page size
307      */
308     private function get_page_size(?int $pagesize) : int {
309         if (null === $pagesize || $pagesize <= 0) {
310             $pagesize = discussion_list_vault::PAGESIZE_DEFAULT;
311         }
313         return $pagesize;
314     }
316     /**
317      * Fetch the current page number (zero-indexed).
318      *
319      * @param   int         $pageno The zero-indexed page number to use
320      * @return  int         The normalised page number
321      */
322     private function get_page_number(?int $pageno) : int {
323         if (null === $pageno || $pageno < 0) {
324             $pageno = 0;
325         }
327         return $pageno;
328     }
330     /**
331      * Get the list of notification for display.
332      *
333      * @param stdClass $user The viewing user
334      * @param int|null $groupid The forum's group id
335      * @return      array
336      */
337     private function get_notifications(stdClass $user, ?int $groupid) : array {
338         $notifications = $this->notifications;
339         $forum = $this->forum;
340         $renderer = $this->renderer;
341         $capabilitymanager = $this->capabilitymanager;
343         if ($forum->is_cutoff_date_reached()) {
344             $notifications[] = (new notification(
345                     get_string('cutoffdatereached', 'forum'),
346                     notification::NOTIFY_INFO
347             ))->set_show_closebutton();
348         } else if ($forum->is_due_date_reached()) {
349             $notifications[] = (new notification(
350                     get_string('thisforumisdue', 'forum', userdate($forum->get_due_date())),
351                     notification::NOTIFY_INFO
352             ))->set_show_closebutton();
353         } else if ($forum->has_due_date()) {
354             $notifications[] = (new notification(
355                     get_string('thisforumhasduedate', 'forum', userdate($forum->get_due_date())),
356                     notification::NOTIFY_INFO
357             ))->set_show_closebutton();
358         }
360         if ($forum->has_blocking_enabled()) {
361             $notifications[] = (new notification(
362                 get_string('thisforumisthrottled', 'forum', [
363                     'blockafter' => $forum->get_block_after(),
364                     'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
365                 ])
366             ))->set_show_closebutton();
367         }
369         if ($forum->is_in_group_mode() && !$capabilitymanager->can_access_all_groups($user)) {
370             if ($groupid === null) {
371                 if (!$capabilitymanager->can_post_to_my_groups($user)) {
372                     $notifications[] = (new notification(
373                         get_string('cannotadddiscussiongroup', 'mod_forum'),
374                         \core\output\notification::NOTIFY_WARNING
375                     ))->set_show_closebutton();
376                 } else {
377                     $notifications[] = (new notification(
378                         get_string('cannotadddiscussionall', 'mod_forum'),
379                         \core\output\notification::NOTIFY_WARNING
380                     ))->set_show_closebutton();
381                 }
382             } else if (!$capabilitymanager->can_access_group($user, $groupid)) {
383                 $notifications[] = (new notification(
384                     get_string('cannotadddiscussion', 'mod_forum'),
385                     \core\output\notification::NOTIFY_WARNING
386                 ))->set_show_closebutton();
387             }
388         }
390         if ('qanda' === $forum->get_type() && !$capabilitymanager->can_manage_forum($user)) {
391             $notifications[] = (new notification(
392                 get_string('qandanotify', 'forum'),
393                 notification::NOTIFY_INFO
394             ))->set_show_closebutton();
395         }
397         if ('eachuser' === $forum->get_type()) {
398             $notifications[] = (new notification(
399                 get_string('allowsdiscussions', 'forum'),
400                 notification::NOTIFY_INFO)
401             )->set_show_closebutton();
402         }
404         return array_map(function($notification) {
405             return $notification->export_for_template($this->renderer);
406         }, $notifications);
407     }