Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
/**
18
 * Discussion renderer.
19
 *
20
 * @package    mod_forum
21
 * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace mod_forum\local\renderers;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use mod_forum\local\entities\discussion as discussion_entity;
30
use mod_forum\local\entities\forum as forum_entity;
31
use mod_forum\local\entities\post as post_entity;
32
use mod_forum\local\entities\sorter as sorter_entity;
33
use mod_forum\local\factories\entity as entity_factory;
34
use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
35
use mod_forum\local\factories\exporter as exporter_factory;
36
use mod_forum\local\factories\url as url_factory;
37
use mod_forum\local\factories\vault as vault_factory;
38
use mod_forum\local\managers\capability as capability_manager;
39
use mod_forum\local\renderers\posts as posts_renderer;
40
use forum_portfolio_caller;
41
use core\output\notification;
42
use context;
43
use context_module;
44
use html_writer;
45
use moodle_exception;
46
use moodle_page;
47
use moodle_url;
48
use rating_manager;
49
use renderer_base;
50
use single_button;
51
use single_select;
52
use stdClass;
53
use url_select;
54
 
55
require_once($CFG->dirroot . '/mod/forum/lib.php');
56
require_once($CFG->dirroot . '/mod/forum/locallib.php');
57
 
58
/**
59
 * Discussion renderer class.
60
 *
61
 * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
62
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
63
 */
64
class discussion {
65
    /** @var forum_entity $forum The forum that the discussion belongs to */
66
    private $forum;
67
    /** @var discussion_entity $discussion The discussion entity */
68
    private $discussion;
69
    /** @var stdClass $discussionrecord Legacy discussion record */
70
    private $discussionrecord;
71
    /** @var stdClass $forumrecord Legacy forum record */
72
    private $forumrecord;
73
    /** @var int $displaymode The display mode to render the discussion in */
74
    private $displaymode;
75
    /** @var renderer_base $renderer Renderer base */
76
    private $renderer;
77
    /** @var posts_renderer $postsrenderer A posts renderer */
78
    private $postsrenderer;
79
    /** @var moodle_page $page The page this discussion is being rendered for */
80
    private $page;
81
    /** @var legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory */
82
    private $legacydatamapperfactory;
83
    /** @var exporter_factory $exporterfactory Exporter factory */
84
    private $exporterfactory;
85
    /** @var vault_factory $vaultfactory Vault factory */
86
    private $vaultfactory;
87
    /** @var url_factory $urlfactory URL factory */
88
    private $urlfactory;
89
    /** @var entity_factory $entityfactory Entity factory */
90
    private $entityfactory;
91
    /** @var capability_manager $capabilitymanager Capability manager */
92
    private $capabilitymanager;
93
    /** @var rating_manager $ratingmanager Rating manager */
94
    private $ratingmanager;
95
    /** @var moodle_url $baseurl The base URL for the discussion */
96
    private $baseurl;
97
    /** @var array $notifications List of HTML notifications to display */
98
    private $notifications;
99
    /** @var sorter_entity $exportedpostsorter Sorter for the exported posts */
100
    private $exportedpostsorter;
101
    /** @var callable $postprocessfortemplate Function to process exported posts before template rendering */
102
    private $postprocessfortemplate;
103
 
104
    /**
105
     * Constructor.
106
     *
107
     * @param forum_entity $forum The forum that the discussion belongs to
108
     * @param discussion_entity $discussion The discussion entity
109
     * @param int $displaymode The display mode to render the discussion in
110
     * @param renderer_base $renderer Renderer base
111
     * @param posts_renderer $postsrenderer A posts renderer
112
     * @param moodle_page $page The page this discussion is being rendered for
113
     * @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
114
     * @param exporter_factory $exporterfactory Exporter factory
115
     * @param vault_factory $vaultfactory Vault factory
116
     * @param url_factory $urlfactory URL factory
117
     * @param entity_factory $entityfactory Entity factory
118
     * @param capability_manager $capabilitymanager Capability manager
119
     * @param rating_manager $ratingmanager Rating manager
120
     * @param sorter_entity $exportedpostsorter Sorter for the exported posts
121
     * @param moodle_url $baseurl The base URL for the discussion
122
     * @param array $notifications List of HTML notifications to display
123
     * @param callable|null $postprocessfortemplate Post processing for template callback
124
     */
125
    public function __construct(
126
        forum_entity $forum,
127
        discussion_entity $discussion,
128
        int $displaymode,
129
        renderer_base $renderer,
130
        posts_renderer $postsrenderer,
131
        moodle_page $page,
132
        legacy_data_mapper_factory $legacydatamapperfactory,
133
        exporter_factory $exporterfactory,
134
        vault_factory $vaultfactory,
135
        url_factory $urlfactory,
136
        entity_factory $entityfactory,
137
        capability_manager $capabilitymanager,
138
        rating_manager $ratingmanager,
139
        sorter_entity $exportedpostsorter,
140
        moodle_url $baseurl,
141
        array $notifications = [],
142
        callable $postprocessfortemplate = null
143
    ) {
144
        $this->forum = $forum;
145
        $this->discussion = $discussion;
146
        $this->displaymode = $displaymode;
147
        $this->renderer = $renderer;
148
        $this->postsrenderer = $postsrenderer;
149
        $this->page = $page;
150
        $this->baseurl = $baseurl;
151
        $this->legacydatamapperfactory = $legacydatamapperfactory;
152
        $this->exporterfactory = $exporterfactory;
153
        $this->vaultfactory = $vaultfactory;
154
        $this->urlfactory = $urlfactory;
155
        $this->entityfactory = $entityfactory;
156
        $this->capabilitymanager = $capabilitymanager;
157
        $this->ratingmanager = $ratingmanager;
158
        $this->notifications = $notifications;
159
 
160
        $this->exportedpostsorter = $exportedpostsorter;
161
        $this->postprocessfortemplate = $postprocessfortemplate;
162
 
163
        $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
164
        $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
165
 
166
        $discussiondatamapper = $this->legacydatamapperfactory->get_discussion_data_mapper();
167
        $this->discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
168
    }
169
 
170
    /**
171
     * Render the discussion for the given user in the specified display mode.
172
     *
173
     * @param stdClass $user The user viewing the discussion
174
     * @param post_entity $firstpost The first post in the discussion
175
     * @param array $replies List of replies to the first post
176
     * @return string HTML for the discussion
177
     */
178
    public function render(
179
        stdClass $user,
180
        post_entity $firstpost,
181
        array $replies
182
    ): string {
183
        global $CFG;
184
 
185
        $displaymode = $this->displaymode;
186
        $capabilitymanager = $this->capabilitymanager;
187
        $urlfactory = $this->urlfactory;
188
        $entityfactory = $this->entityfactory;
189
 
190
        // Make sure we can render.
191
        if (!$capabilitymanager->can_view_discussions($user)) {
192
            throw new moodle_exception('noviewdiscussionspermission', 'mod_forum');
193
        }
194
 
195
        $posts = array_merge([$firstpost], array_values($replies));
196
 
197
        if ($this->postprocessfortemplate !== null) {
198
            $exporteddiscussion = ($this->postprocessfortemplate) ($this->discussion, $user, $this->forum);
199
        } else {
200
            $exporteddiscussion = $this->get_exported_discussion($user);
201
        }
202
 
203
        $hasanyactions = false;
204
        $hasanyactions = $hasanyactions || $capabilitymanager->can_favourite_discussion($user);
205
        $hasanyactions = $hasanyactions || $capabilitymanager->can_pin_discussions($user);
206
        $hasanyactions = $hasanyactions || $capabilitymanager->can_manage_forum($user);
207
 
208
        $exporteddiscussion = array_merge($exporteddiscussion, [
209
            'notifications' => $this->get_notifications($user),
210
            'html' => [
211
                'hasanyactions' => $hasanyactions,
212
                'posts' => $this->postsrenderer->render($user, [$this->forum], [$this->discussion], $posts),
213
                'modeselectorform' => $this->get_display_mode_selector_html($displaymode, $user),
214
                'subscribe' => null,
215
                'movediscussion' => null,
216
                'pindiscussion' => null,
217
                'neighbourlinks' => $this->get_neighbour_links_html(),
218
                'exportdiscussion' => !empty($CFG->enableportfolios) ? $this->get_export_discussion_html($user) : null
219
            ],
220
            'settingsselector' => true,
221
        ]);
222
 
223
        $capabilities = (array) $exporteddiscussion['capabilities'];
224
 
225
        if ($capabilities['move']) {
226
            $exporteddiscussion['html']['movediscussion'] = $this->get_move_discussion_html();
227
        }
228
 
229
        if (!empty($user->id)) {
230
            $loggedinuser = $entityfactory->get_author_from_stdClass($user);
231
            $exporteddiscussion['loggedinuser'] = [
232
                'firstname' => $loggedinuser->get_first_name(),
233
                'fullname' => $loggedinuser->get_full_name(),
234
                'profileimageurl' => ($urlfactory->get_author_profile_image_url($loggedinuser, null))->out(false)
235
            ];
236
        }
237
 
238
        $exporteddiscussion['throttlingwarningmsg'] = '';
239
        $cmrecord = $this->forum->get_course_module_record();
240
        if (($warningobj = forum_check_throttling($this->forumrecord, $cmrecord)) && $warningobj->canpost) {
241
            $throttlewarnnotification = (new notification(
242
                    get_string($warningobj->errorcode, $warningobj->module, $warningobj->additional)
243
            ))->set_show_closebutton();
244
            $exporteddiscussion['throttlingwarningmsg'] = $throttlewarnnotification->get_message();
245
        }
246
 
247
        if ($this->displaymode === FORUM_MODE_NESTED_V2) {
248
            $template = 'mod_forum/forum_discussion_nested_v2';
249
        } else {
250
            $template = 'mod_forum/forum_discussion';
251
        }
252
 
253
        return $this->renderer->render_from_template($template, $exporteddiscussion);
254
    }
255
 
256
    /**
257
     * Get the groups details for all groups available to the forum.
258
     *
259
     * @return  stdClass[]
260
     */
261
    private function get_groups_available_in_forum(): array {
262
        $course = $this->forum->get_course_record();
263
        $coursemodule = $this->forum->get_course_module_record();
264
 
265
        return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
266
    }
267
 
268
    /**
269
     * Get the exported discussion.
270
     *
271
     * @param stdClass $user The user viewing the discussion
272
     * @return array
273
     */
274
    private function get_exported_discussion(stdClass $user): array {
275
        $discussionexporter = $this->exporterfactory->get_discussion_exporter(
276
            $user,
277
            $this->forum,
278
            $this->discussion,
279
            $this->get_groups_available_in_forum()
280
        );
281
 
282
        return (array) $discussionexporter->export($this->renderer);
283
    }
284
 
285
    /**
286
     * Get the HTML for the display mode selector.
287
     *
288
     * @param int $displaymode The current display mode
289
     * @param stdClass $user The current user
290
     * @return string
291
     */
292
    private function get_display_mode_selector_html(int $displaymode, stdClass $user): string {
293
        $baseurl = $this->baseurl;
294
        $select = new single_select(
295
            $baseurl,
296
            'mode',
297
            forum_get_layout_modes(get_user_preferences('forum_useexperimentalui', false, $user)),
298
            $displaymode,
299
            null,
300
            'mode'
301
        );
302
        $select->set_label(get_string('displaymode', 'forum'), ['class' => 'accesshide']);
303
 
304
        return $this->renderer->render($select);
305
    }
306
 
307
    /**
308
     * Get the HTML to render the move discussion selector and button.
309
     *
310
     * @return string
311
     */
312
    private function get_move_discussion_html(): ?string {
313
        global $DB;
314
 
315
        $forum = $this->forum;
316
        $discussion = $this->discussion;
317
        $courseid = $forum->get_course_id();
318
 
319
        // Popup menu to move discussions to other forums. The discussion in a
320
        // single discussion forum can't be moved.
321
        $modinfo = get_fast_modinfo($courseid);
322
        if (isset($modinfo->instances['forum'])) {
323
            $forummenu = [];
324
            // Check forum types and eliminate simple discussions.
325
            $forumcheck = $DB->get_records('forum', ['course' => $courseid], '', 'id, type');
326
            foreach ($modinfo->instances['forum'] as $forumcm) {
327
                if (!$forumcm->uservisible || !has_capability('mod/forum:startdiscussion',
328
                    context_module::instance($forumcm->id))) {
329
                    continue;
330
                }
331
                $section = $forumcm->sectionnum;
332
                $sectionname = get_section_name($courseid, $section);
333
                if (empty($forummenu[$section])) {
334
                    $forummenu[$section] = [$sectionname => []];
335
                }
336
                $forumidcompare = $forumcm->instance != $forum->get_id();
337
                $forumtypecheck = $forumcheck[$forumcm->instance]->type !== 'single';
338
 
339
                if ($forumidcompare and $forumtypecheck) {
340
                    $url = "/mod/forum/discuss.php?d={$discussion->get_id()}&move=$forumcm->instance&sesskey=".sesskey();
341
                    $forummenu[$section][$sectionname][$url] = format_string($forumcm->name);
342
                }
343
            }
344
            if (!empty($forummenu)) {
345
                $html = '<div class="movediscussionoption">';
346
 
347
                $movebutton = get_string('move');
348
                if ($this->displaymode === FORUM_MODE_NESTED_V2) {
349
                    // Move discussion selector will be rendered on the settings drawer. We won't output the button in this mode.
350
                    $movebutton = null;
351
                }
352
                $select = new url_select($forummenu, '',
353
                        ['/mod/forum/discuss.php?d=' . $discussion->get_id() => get_string("movethisdiscussionto", "forum")],
354
                        'forummenu', $movebutton);
355
                $select->set_label(get_string('movethisdiscussionlabel', 'mod_forum'), [
356
                    'class' => 'sr-only',
357
                ]);
358
                $html .= $this->renderer->render($select);
359
                $html .= "</div>";
360
                return $html;
361
            }
362
        }
363
 
364
        return null;
365
    }
366
 
367
    /**
368
     * Get the HTML to render the export discussion button.
369
     *
370
     * @param   stdClass $user The user viewing the discussion
371
     * @return  string|null
372
     */
373
    private function get_export_discussion_html(stdClass $user): ?string {
374
        global $CFG;
375
 
376
        if (!$this->capabilitymanager->can_export_discussions($user)) {
377
            return null;
378
        }
379
 
380
        $button = new \portfolio_add_button();
381
        $button->set_callback_options('forum_portfolio_caller', ['discussionid' => $this->discussion->get_id()], 'mod_forum');
382
        $button = $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportdiscussion', 'mod_forum'));
383
        return $button ?: null;
384
    }
385
 
386
    /**
387
     * Get a list of notification HTML to render in the page.
388
     *
389
     * @param stdClass $user The user viewing the discussion
390
     * @return string[]
391
     */
392
    private function get_notifications($user): array {
393
        $notifications = $this->notifications;
394
        $discussion = $this->discussion;
395
        $forum = $this->forum;
396
 
397
        if ($forum->is_cutoff_date_reached()) {
398
            $notifications[] = (new notification(
399
                    get_string('cutoffdatereached', 'forum'),
400
                    notification::NOTIFY_INFO
401
            ))->set_show_closebutton();
402
        } else if ($forum->get_type() != 'single') {
403
            // Due date is already shown at the top of the page for single simple discussion forums.
404
            if ($forum->is_due_date_reached()) {
405
                $notifications[] = (new notification(
406
                    get_string('thisforumisdue', 'forum', userdate($forum->get_due_date())),
407
                    notification::NOTIFY_INFO
408
                ))->set_show_closebutton();
409
            } else if ($forum->has_due_date()) {
410
                $notifications[] = (new notification(
411
                    get_string('thisforumhasduedate', 'forum', userdate($forum->get_due_date())),
412
                    notification::NOTIFY_INFO
413
                ))->set_show_closebutton();
414
            }
415
        }
416
 
417
        if ($forum->is_discussion_locked($discussion)) {
418
            $notifications[] = (new notification(
419
                get_string('discussionlocked', 'forum'),
420
                notification::NOTIFY_INFO
421
            ))
422
            ->set_extra_classes(['discussionlocked'])
423
            ->set_show_closebutton();
424
        }
425
 
426
        if ($forum->get_type() == 'qanda') {
427
            if ($this->capabilitymanager->must_post_before_viewing_discussion($user, $discussion)) {
428
                $notifications[] = (new notification(
429
                    get_string('qandanotify', 'forum')
430
                ))->set_show_closebutton(true)->set_extra_classes(['mt-3']);
431
            }
432
        }
433
 
434
        if ($forum->has_blocking_enabled()) {
435
            $notifications[] = (new notification(
436
                get_string('thisforumisthrottled', 'forum', [
437
                    'blockafter' => $forum->get_block_after(),
438
                    'blockperiod' => get_string('secondstotime' . $forum->get_block_period())
439
                ]),
440
                notification::NOTIFY_INFO
441
            ))->set_show_closebutton();
442
 
443
        }
444
 
445
        return array_map(function($notification) {
446
            return $notification->export_for_template($this->renderer);
447
        }, $notifications);
448
    }
449
 
450
    /**
451
     * Get HTML to display the neighbour links.
452
     *
453
     * @return string
454
     */
455
    private function get_neighbour_links_html(): string {
456
        $forum = $this->forum;
457
        $coursemodule = $forum->get_course_module_record();
458
        $neighbours = forum_get_discussion_neighbours($coursemodule, $this->discussionrecord, $this->forumrecord);
459
        return $this->renderer->neighbouring_discussion_navigation($neighbours['prev'], $neighbours['next']);
460
    }
461
}