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 * The post exporter tests.
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
25defined('MOODLE_INTERNAL') || die();
26
27use \mod_forum\local\entities\discussion as discussion_entity;
28use \mod_forum\local\entities\post as post_entity;
29use \mod_forum\local\exporters\post as post_exporter;
30use \mod_forum\local\managers\capability as capability_manager;
31
32global $CFG;
33require_once(__DIR__ . '/generator_trait.php');
34require_once($CFG->dirroot . '/rating/lib.php');
35
36/**
37 * The post exporter tests.
38 *
39 * @package    mod_forum
40 * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
41 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 */
43class mod_forum_exporters_post_testcase extends advanced_testcase {
44    // Make use of the test generator trait.
45    use mod_forum_tests_generator_trait;
46
47    /**
48     * Test the export function returns expected values.
49     *
50     * @dataProvider export_post_provider
51     * @param bool $istimed True if this is a timed post
52     * @param int $addtime Seconds to be added to the current time
53     */
54    public function test_export_post($istimed = false, $addtime = 0) {
55        global $CFG, $PAGE;
56        $this->resetAfterTest();
57
58        $CFG->enableportfolios = true;
59        $filestorage = get_file_storage();
60        $renderer = $PAGE->get_renderer('core');
61        $datagenerator = $this->getDataGenerator();
62        $forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
63        $user = $datagenerator->create_user();
64        $course = $datagenerator->create_course();
65        $forum = $datagenerator->create_module('forum', ['course' => $course->id]);
66        $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
67        $context = context_module::instance($coursemodule->id);
68        $now = time();
69
70        $forumgenparams = [
71            'course' => $forum->course,
72            'userid' => $user->id,
73            'forum' => $forum->id,
74        ];
75        if ($istimed) {
76            $forumgenparams['timestart'] = $now + $addtime;
77        }
78        $discussion = $forumgenerator->create_discussion((object) $forumgenparams);
79
80        $post = $forumgenerator->create_post((object) [
81            'discussion' => $discussion->id,
82            'parent' => 0,
83            'userid' => $user->id,
84            'created' => $now,
85            'modified' => $now,
86            'subject' => 'This is the subject',
87            'message' => 'This is the message',
88            'messagetrust' => 1,
89            'attachment' => 0,
90            'totalscore' => 0,
91            'mailnow' => 1,
92            'deleted' => 0
93        ]);
94
95        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['foo', 'bar']);
96        $tags = \core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id);
97        $attachment = $filestorage->create_file_from_string(
98            [
99                'contextid' => $context->id,
100                'component' => 'mod_forum',
101                'filearea'  => 'attachment',
102                'itemid'    => $post->id,
103                'filepath'  => '/',
104                'filename'  => 'example1.jpg',
105            ],
106            'image contents'
107        );
108
109        $canview = true;
110        $canedit = true;
111        $candelete = true;
112        $cansplit = true;
113        $canreply = true;
114        $canexport = true;
115        $cancontrolreadstatus = true;
116        $canreplyprivately = true;
117        $canenrol = true;
118        $capabilitymanager = new test_capability_manager(
119            $canview,
120            $canedit,
121            $candelete,
122            $cansplit,
123            $canreply,
124            $canexport,
125            $cancontrolreadstatus,
126            $canreplyprivately,
127            $canenrol
128        );
129        $managerfactory = \mod_forum\local\container::get_manager_factory();
130        $entityfactory = \mod_forum\local\container::get_entity_factory();
131        $forum = $entityfactory->get_forum_from_stdclass($forum, $context, $coursemodule, $course);
132        $discussion = $entityfactory->get_discussion_from_stdclass($discussion);
133        $post = $entityfactory->get_post_from_stdclass($post);
134        $author = $entityfactory->get_author_from_stdclass($user);
135        $authorcontext = context_user::instance($author->get_id());
136
137        $exporter = new post_exporter($post, [
138            'legacydatamapperfactory' => \mod_forum\local\container::get_legacy_data_mapper_factory(),
139            'capabilitymanager' => $capabilitymanager,
140            'readreceiptcollection' => null,
141            'urlfactory' => \mod_forum\local\container::get_url_factory(),
142            'forum' => $forum,
143            'discussion' => $discussion,
144            'author' => $author,
145            'authorcontextid' => $authorcontext->id,
146            'user' => $user,
147            'context' => $context,
148            'authorgroups' => [],
149            'attachments' => [$attachment],
150            'tags' => $tags,
151            'rating' => null,
152            'includehtml' => true
153        ]);
154
155        $exportedpost = $exporter->export($renderer);
156
157        $this->assertEquals('This is the subject', $exportedpost->subject);
158        $this->assertEquals('This is the message', $exportedpost->message);
159        $this->assertEquals($user->id, $exportedpost->author->id);
160        $this->assertEquals($discussion->get_id(), $exportedpost->discussionid);
161        $this->assertEquals(false, $exportedpost->hasparent);
162        $this->assertEquals(null, $exportedpost->parentid);
163        if ($istimed && ($addtime > 0)) {
164            $this->assertEquals($now + $addtime, $exportedpost->timecreated);
165        } else {
166            $this->assertEquals($now, $exportedpost->timecreated);
167        }
168        $this->assertEquals(null, $exportedpost->unread);
169        $this->assertEquals(false, $exportedpost->isdeleted);
170        $this->assertEquals($canview, $exportedpost->capabilities['view']);
171        $this->assertEquals($canedit, $exportedpost->capabilities['edit']);
172        $this->assertEquals($candelete, $exportedpost->capabilities['delete']);
173        $this->assertEquals($cansplit, $exportedpost->capabilities['split']);
174        $this->assertEquals($canreply, $exportedpost->capabilities['reply']);
175        $this->assertEquals($canexport, $exportedpost->capabilities['export']);
176        $this->assertEquals($canenrol, $exportedpost->capabilities['selfenrol']);
177        $this->assertEquals($cancontrolreadstatus, $exportedpost->capabilities['controlreadstatus']);
178        $this->assertNotEmpty($exportedpost->urls['view']);
179        $this->assertNotEmpty($exportedpost->urls['viewisolated']);
180        $this->assertNotEmpty($exportedpost->urls['edit']);
181        $this->assertNotEmpty($exportedpost->urls['delete']);
182        $this->assertNotEmpty($exportedpost->urls['split']);
183        $this->assertNotEmpty($exportedpost->urls['reply']);
184        $this->assertNotEmpty($exportedpost->urls['markasread']);
185        $this->assertNotEmpty($exportedpost->urls['markasunread']);
186        $this->assertCount(1, $exportedpost->attachments);
187        $this->assertEquals('example1.jpg', $exportedpost->attachments[0]->filename);
188        $this->assertCount(2, $exportedpost->tags);
189        $this->assertEquals('foo', $exportedpost->tags[0]['displayname']);
190        $this->assertEquals('bar', $exportedpost->tags[1]['displayname']);
191        $this->assertEquals(null, $exportedpost->html['rating']);
192        $this->assertNotEquals(null, $exportedpost->html['taglist']);
193        $this->assertNotEmpty($exportedpost->html['authorsubheading']);
194    }
195
196    /**
197     * Data provider for test_export_post().
198     *
199     * @return array
200     */
201    public function export_post_provider(): array {
202        return [
203            'Simple export' => [
204            ],
205            'Test timed post future' => [
206                true,
207                1000
208            ],
209            'Test timed post past' => [
210                true,
211                -1000
212            ],
213        ];
214    }
215
216    /**
217     * Test exporting of a deleted post.
218     */
219    public function test_export_deleted_post() {
220        global $CFG, $PAGE;
221        $this->resetAfterTest();
222
223        $CFG->enableportfolios = true;
224        $filestorage = get_file_storage();
225        $renderer = $PAGE->get_renderer('core');
226        $datagenerator = $this->getDataGenerator();
227        $forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
228        $user = $datagenerator->create_user();
229        $course = $datagenerator->create_course();
230        $forum = $datagenerator->create_module('forum', ['course' => $course->id]);
231        $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
232        $context = context_module::instance($coursemodule->id);
233        $discussion = $forumgenerator->create_discussion((object) [
234            'course' => $forum->course,
235            'userid' => $user->id,
236            'forum' => $forum->id
237        ]);
238        $now = time();
239        $post = $forumgenerator->create_post((object) [
240            'discussion' => $discussion->id,
241            'parent' => 0,
242            'userid' => $user->id,
243            'created' => $now,
244            'modified' => $now,
245            'subject' => 'This is the subject',
246            'message' => 'This is the message',
247            'messagetrust' => 1,
248            'attachment' => 0,
249            'totalscore' => 0,
250            'mailnow' => 1,
251            'deleted' => 1
252        ]);
253
254        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['foo', 'bar']);
255        $tags = \core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id);
256        $attachment = $filestorage->create_file_from_string(
257            [
258                'contextid' => $context->id,
259                'component' => 'mod_forum',
260                'filearea'  => 'attachment',
261                'itemid'    => $post->id,
262                'filepath'  => '/',
263                'filename'  => 'example1.jpg',
264            ],
265            'image contents'
266        );
267
268        $canview = true;
269        $canedit = true;
270        $candelete = true;
271        $cansplit = true;
272        $canreply = true;
273        $canexport = true;
274        $cancontrolreadstatus = true;
275        $capabilitymanager = new test_capability_manager(
276            $canview,
277            $canedit,
278            $candelete,
279            $cansplit,
280            $canreply,
281            $canexport,
282            $cancontrolreadstatus
283        );
284        $managerfactory = \mod_forum\local\container::get_manager_factory();
285        $entityfactory = \mod_forum\local\container::get_entity_factory();
286        $forum = $entityfactory->get_forum_from_stdclass($forum, $context, $coursemodule, $course);
287        $discussion = $entityfactory->get_discussion_from_stdclass($discussion);
288        $post = $entityfactory->get_post_from_stdclass($post);
289        $author = $entityfactory->get_author_from_stdclass($user);
290        $authorcontext = context_user::instance($author->get_id());
291
292        $exporter = new post_exporter($post, [
293            'legacydatamapperfactory' => \mod_forum\local\container::get_legacy_data_mapper_factory(),
294            'capabilitymanager' => $capabilitymanager,
295            'readreceiptcollection' => null,
296            'urlfactory' => \mod_forum\local\container::get_url_factory(),
297            'forum' => $forum,
298            'discussion' => $discussion,
299            'author' => $author,
300            'authorcontextid' => $authorcontext->id,
301            'user' => $user,
302            'context' => $context,
303            'authorgroups' => [],
304            'attachments' => [$attachment],
305            'tags' => $tags,
306            'rating' => null,
307            'includehtml' => true
308        ]);
309
310        $exportedpost = $exporter->export($renderer);
311
312        $this->assertNotEquals('This is the subject', $exportedpost->subject);
313        $this->assertNotEquals('This is the message', $exportedpost->message);
314        $this->assertEquals(null, $exportedpost->timecreated);
315        $this->assertEquals(null, $exportedpost->unread);
316        $this->assertEquals(true, $exportedpost->isdeleted);
317        $this->assertEquals([], $exportedpost->attachments);
318        $this->assertEquals([], $exportedpost->tags);
319        $this->assertEquals(null, $exportedpost->html['rating']);
320        $this->assertEquals(null, $exportedpost->html['taglist']);
321        $this->assertEquals(null, $exportedpost->html['authorsubheading']);
322    }
323
324    /**
325     * Test exporting of a post the user can't view.
326     */
327    public function test_export_post_no_view_capability() {
328        global $CFG, $PAGE;
329        $this->resetAfterTest();
330
331        $CFG->enableportfolios = true;
332        $filestorage = get_file_storage();
333        $renderer = $PAGE->get_renderer('core');
334        $datagenerator = $this->getDataGenerator();
335        $forumgenerator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
336        $user = $datagenerator->create_user();
337        $course = $datagenerator->create_course();
338        $forum = $datagenerator->create_module('forum', ['course' => $course->id]);
339        $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
340        $context = context_module::instance($coursemodule->id);
341        $discussion = $forumgenerator->create_discussion((object) [
342            'course' => $forum->course,
343            'userid' => $user->id,
344            'forum' => $forum->id
345        ]);
346        $now = time();
347        $post = $forumgenerator->create_post((object) [
348            'discussion' => $discussion->id,
349            'parent' => 0,
350            'userid' => $user->id,
351            'created' => $now,
352            'modified' => $now,
353            'subject' => 'This is the subject',
354            'message' => 'This is the message',
355            'messagetrust' => 1,
356            'attachment' => 0,
357            'totalscore' => 0,
358            'mailnow' => 1,
359            'deleted' => 0
360        ]);
361
362        \core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, $context, ['foo', 'bar']);
363        $tags = \core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id);
364        $attachment = $filestorage->create_file_from_string(
365            [
366                'contextid' => $context->id,
367                'component' => 'mod_forum',
368                'filearea'  => 'attachment',
369                'itemid'    => $post->id,
370                'filepath'  => '/',
371                'filename'  => 'example1.jpg',
372            ],
373            'image contents'
374        );
375
376        $canview = false;
377        $canedit = true;
378        $candelete = true;
379        $cansplit = true;
380        $canreply = true;
381        $canexport = true;
382        $cancontrolreadstatus = true;
383        $capabilitymanager = new test_capability_manager(
384            $canview,
385            $canedit,
386            $candelete,
387            $cansplit,
388            $canreply,
389            $canexport,
390            $cancontrolreadstatus
391        );
392        $managerfactory = \mod_forum\local\container::get_manager_factory();
393        $entityfactory = \mod_forum\local\container::get_entity_factory();
394        $forum = $entityfactory->get_forum_from_stdclass($forum, $context, $coursemodule, $course);
395        $discussion = $entityfactory->get_discussion_from_stdclass($discussion);
396        $post = $entityfactory->get_post_from_stdclass($post);
397        $author = $entityfactory->get_author_from_stdclass($user);
398        $authorcontext = context_user::instance($author->get_id());
399
400        $exporter = new post_exporter($post, [
401            'legacydatamapperfactory' => \mod_forum\local\container::get_legacy_data_mapper_factory(),
402            'capabilitymanager' => $capabilitymanager,
403            'readreceiptcollection' => null,
404            'urlfactory' => \mod_forum\local\container::get_url_factory(),
405            'forum' => $forum,
406            'discussion' => $discussion,
407            'author' => $author,
408            'authorcontextid' => $authorcontext->id,
409            'user' => $user,
410            'context' => $context,
411            'authorgroups' => [],
412            'attachments' => [$attachment],
413            'tags' => $tags,
414            'rating' => null,
415            'includehtml' => true
416        ]);
417
418        $exportedpost = $exporter->export($renderer);
419
420        $this->assertNotEquals('This is the subject', $exportedpost->subject);
421        $this->assertNotEquals('This is the message', $exportedpost->message);
422        $this->assertEquals(null, $exportedpost->timecreated);
423        $this->assertEquals(null, $exportedpost->unread);
424        $this->assertEquals(false, $exportedpost->isdeleted);
425        $this->assertEquals([], $exportedpost->attachments);
426        $this->assertEquals([], $exportedpost->tags);
427        $this->assertEquals(null, $exportedpost->html['rating']);
428        $this->assertEquals(null, $exportedpost->html['taglist']);
429        $this->assertEquals(null, $exportedpost->html['authorsubheading']);
430    }
431}
432
433/**
434 * Test implementation of the capability manager.
435 *
436 * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
437 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
438 */
439class test_capability_manager extends capability_manager {
440    /** @var bool $view Value for can_view_post */
441    private $view;
442    /** @var bool $edit Value for can_edit_post */
443    private $edit;
444    /** @var bool $delete Value for can_delete_post */
445    private $delete;
446    /** @var bool $split Value for can_split_post */
447    private $split;
448    /** @var bool $reply Value for can_reply_to_post */
449    private $reply;
450    /** @var bool $export Value for can_export_post */
451    private $export;
452    /** @var bool $controlreadstatus Value for can_manually_control_post_read_status */
453    private $controlreadstatus;
454    /** @var bool $controlreadstatus Value for can_reply_privately_to_post */
455    private $canreplyprivatelytopost;
456    /** @var bool $canenrol Value for can_self_enrol */
457    private $canenrol;
458
459    /**
460     * Constructor.
461     *
462     * @param bool $view Value for can_view_post
463     * @param bool $edit Value for can_edit_post
464     * @param bool $delete Value for can_delete_post
465     * @param bool $split Value for can_split_post
466     * @param bool $reply Value for can_reply_to_post
467     * @param bool $export Value for can_export_post
468     * @param bool $controlreadstatus Value for can_manually_control_post_read_status
469     */
470    public function __construct(
471        bool $view = true,
472        bool $edit = true,
473        bool $delete = true,
474        bool $split = true,
475        bool $reply = true,
476        bool $export = true,
477        bool $controlreadstatus = true,
478        bool $canreplyprivatelytopost = true,
479        bool $canenrol = true
480    ) {
481        $this->view = $view;
482        $this->edit = $edit;
483        $this->delete = $delete;
484        $this->split = $split;
485        $this->reply = $reply;
486        $this->export = $export;
487        $this->controlreadstatus = $controlreadstatus;
488        $this->canreplyprivatelytopost = $canreplyprivatelytopost;
489        $this->canenrol = $canenrol;
490    }
491
492    /**
493     * Override can_view_post
494     *
495     * @param stdClass $user The user
496     * @param discussion_entity $discussion The discussion
497     * @param post_entity $post The post
498     * @return bool
499     */
500    public function can_view_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
501        return $this->view;
502    }
503
504    /**
505     * Override can_edit_post
506     *
507     * @param stdClass $user The user
508     * @param discussion_entity $discussion The discussion
509     * @param post_entity $post The post
510     * @return bool
511     */
512    public function can_edit_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
513        return $this->edit;
514    }
515
516    /**
517     * Override can_delete_post
518     *
519     * @param stdClass $user The user
520     * @param discussion_entity $discussion The discussion
521     * @param post_entity $post The post
522     * @param bool $hasreplies
523     * @return bool
524     */
525    public function can_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,
526                                    bool $hasreplies = false) : bool {
527        return $this->delete;
528    }
529
530    /**
531     * Override can_split_post
532     *
533     * @param stdClass $user The user
534     * @param discussion_entity $discussion The discussion
535     * @param post_entity $post The post
536     * @return bool
537     */
538    public function can_split_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
539        return $this->split;
540    }
541
542    /**
543     * Override can_reply_to_post
544     *
545     * @param stdClass $user The user
546     * @param discussion_entity $discussion The discussion
547     * @param post_entity $post The post
548     * @return bool
549     */
550    public function can_reply_to_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
551        return $this->reply;
552    }
553
554    /**
555     * Override can_export_post
556     *
557     * @param stdClass $user The user
558     * @param post_entity $post The post
559     * @return bool
560     */
561    public function can_export_post(stdClass $user, post_entity $post) : bool {
562        return $this->export;
563    }
564
565    /**
566     * Override can_manually_control_post_read_status
567     *
568     * @param stdClass $user The user
569     * @return bool
570     */
571    public function can_manually_control_post_read_status(stdClass $user) : bool {
572        return $this->controlreadstatus;
573    }
574
575    /**
576     * Override can_reply_privately_to_post
577     * @param stdClass $user
578     * @param post_entity $post
579     * @return bool
580     */
581    public function can_reply_privately_to_post(stdClass $user, post_entity $post) : bool {
582        return $this->canreplyprivatelytopost;
583    }
584
585    /**
586     * Override can_self_enrol
587     * @param stdClass $user
588     * @return bool
589     */
590    public function can_self_enrol(stdClass $user) : bool {
591        return $this->canenrol;
592    }
593}
594