6e49a629aba0e5293d05afbfe84a170992cb4a5e
[moodle.git] / mod / forum / classes / local / builders / exported_discussion_summaries.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  * Exported discussion summaries builder class.
19  *
20  * @package    mod_forum
21  * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace mod_forum\local\builders;
27 defined('MOODLE_INTERNAL') || die();
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\factories\legacy_data_mapper as legacy_data_mapper_factory;
33 use mod_forum\local\factories\exporter as exporter_factory;
34 use mod_forum\local\factories\vault as vault_factory;
35 use mod_forum\local\factories\manager as manager_factory;
36 use rating_manager;
37 use renderer_base;
38 use stdClass;
40 /**
41  * Exported discussion summaries builder class.
42  *
43  * This class is an implementation of the builder pattern (loosely). It is responsible
44  * for taking a set of related forums, discussions, and posts and generate the exported
45  * version of the discussion summaries.
46  *
47  * It encapsulates the complexity involved with exporting discussions summaries. All of the relevant
48  * additional resources will be loaded by this class in order to ensure the exporting
49  * process can happen.
50  *
51  * See this doc for more information on the builder pattern:
52  * https://designpatternsphp.readthedocs.io/en/latest/Creational/Builder/README.html
53  *
54  * @package    mod_forum
55  * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
56  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
57  */
58 class exported_discussion_summaries {
59     /** @var renderer_base $renderer Core renderer */
60     private $renderer;
62     /** @var legacy_data_mapper_factory $legacydatamapperfactory Data mapper factory */
63     private $legacydatamapperfactory;
65     /** @var exporter_factory $exporterfactory Exporter factory */
66     private $exporterfactory;
68     /** @var vault_factory $vaultfactory Vault factory */
69     private $vaultfactory;
71     /** @var manager_factory $managerfactory Manager factory */
72     private $managerfactory;
74     /** @var rating_manager $ratingmanager Rating manager */
75     private $ratingmanager;
77     /**
78      * Constructor.
79      *
80      * @param renderer_base $renderer Core renderer
81      * @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
82      * @param exporter_factory $exporterfactory Exporter factory
83      * @param vault_factory $vaultfactory Vault factory
84      * @param manager_factory $managerfactory Manager factory
85      */
86     public function __construct(
87         renderer_base $renderer,
88         legacy_data_mapper_factory $legacydatamapperfactory,
89         exporter_factory $exporterfactory,
90         vault_factory $vaultfactory,
91         manager_factory $managerfactory
92     ) {
93         $this->renderer = $renderer;
94         $this->legacydatamapperfactory = $legacydatamapperfactory;
95         $this->exporterfactory = $exporterfactory;
96         $this->vaultfactory = $vaultfactory;
97         $this->managerfactory = $managerfactory;
98         $this->ratingmanager = $managerfactory->get_rating_manager();
99     }
101     /**
102      * Build the exported discussion summaries for a given set of discussions.
103      *
104      * This will typically be used for a list of discussions in the same forum.
105      *
106      * @param stdClass $user The user to export the posts for.
107      * @param forum_entity $forum The forum that each of the $discussions belong to
108      * @param discussion_summary_entity[] $discussions A list of all discussion summaries to export
109      * @return stdClass[] List of exported posts in the same order as the $posts array.
110      */
111     public function build(
112         stdClass $user,
113         forum_entity $forum,
114         array $discussions
115     ) : array {
116         $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
117         $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
119         $discussionids = array_keys($discussions);
121         $postvault = $this->vaultfactory->get_post_vault();
122         $posts = $postvault->get_from_discussion_ids($user, $discussionids, $canseeanyprivatereply);
123         $groupsbyid = $this->get_groups_available_in_forum($forum);
124         $groupsbyauthorid = $this->get_author_groups_from_posts($posts, $forum);
126         $replycounts = $postvault->get_reply_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
127         $latestposts = $postvault->get_latest_posts_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
128         $latestauthors = $this->get_latest_posts_authors($latestposts);
129         $latestpostsids = array_map(function($post) {
130             return $post->get_id();
131         }, $latestposts);
133         $postauthorids = array_unique(array_reduce($discussions, function($carry, $summary) use ($latestposts){
134             $firstpostauthorid = $summary->get_first_post_author()->get_id();
135             $discussion = $summary->get_discussion();
136             $lastpostauthorid = $latestposts[$discussion->get_id()]->get_author_id();
137             return array_merge($carry, [$firstpostauthorid, $lastpostauthorid]);
138         }, []));
139         $postauthorcontextids = $this->get_author_context_ids($postauthorids);
141         $unreadcounts = [];
142         $favourites = $this->get_favourites($user);
143         $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
144         $forumrecord = $forumdatamapper->to_legacy_object($forum);
146         if (forum_tp_can_track_forums($forumrecord)) {
147             $unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
148         }
150         $summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
151             $user,
152             $forum,
153             $discussions,
154             $groupsbyid,
155             $groupsbyauthorid,
156             $replycounts,
157             $unreadcounts,
158             $latestpostsids,
159             $postauthorcontextids,
160             $favourites,
161             $latestauthors
162         );
164         $exportedposts = (array) $summaryexporter->export($this->renderer);
165         $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
167         array_walk($exportedposts['summaries'], function($summary) use ($firstposts, $latestposts) {
168             $summary->discussion->times['created'] = (int) $firstposts[$summary->discussion->firstpostid]->get_time_created();
169             $summary->discussion->times['modified'] = (int) $latestposts[$summary->discussion->id]->get_time_created();
170         });
172         // Pass the current, preferred sort order for the discussions list.
173         $discussionlistvault = $this->vaultfactory->get_discussions_in_forum_vault();
174         $sortorder = get_user_preferences('forum_discussionlistsortorder',
175             $discussionlistvault::SORTORDER_LASTPOST_DESC);
177         $sortoptions = array(
178             'islastpostdesc' => $sortorder == $discussionlistvault::SORTORDER_LASTPOST_DESC,
179             'islastpostasc' => $sortorder == $discussionlistvault::SORTORDER_LASTPOST_ASC,
180             'isrepliesdesc' => $sortorder == $discussionlistvault::SORTORDER_REPLIES_DESC,
181             'isrepliesasc' => $sortorder == $discussionlistvault::SORTORDER_REPLIES_ASC,
182             'iscreateddesc' => $sortorder == $discussionlistvault::SORTORDER_CREATED_DESC,
183             'iscreatedasc' => $sortorder == $discussionlistvault::SORTORDER_CREATED_ASC
184         );
186         $exportedposts['state']['sortorder'] = $sortoptions;
188         return $exportedposts;
189     }
191     /**
192      * Get a list of all favourited discussions.
193      *
194      * @param stdClass $user The user we are getting favourites for
195      * @return int[] A list of favourited itemids
196      */
197     private function get_favourites(stdClass $user) : array {
198         $ids = [];
200         if (isloggedin()) {
201             $usercontext = \context_user::instance($user->id);
202             $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
203             $favourites = $ufservice->find_favourites_by_type('mod_forum', 'discussions');
204             foreach ($favourites as $favourite) {
205                 $ids[] = $favourite->itemid;
206             }
207         }
209         return $ids;
210     }
212     /**
213      * Returns a mapped array of discussionid to the authors of the latest post
214      *
215      * @param array $latestposts Mapped array of discussion to latest posts.
216      * @return array Array of authors mapped to the discussion
217      */
218     private function get_latest_posts_authors($latestposts) {
219         $authors = $this->vaultfactory->get_author_vault()->get_authors_for_posts($latestposts);
221         $mappedauthors = array_reduce($latestposts, function($carry, $item) use ($authors) {
222             $carry[$item->get_discussion_id()] = $authors[$item->get_author_id()];
224             return $carry;
225         }, []);
226         return $mappedauthors;
227     }
229     /**
230      * Get the groups details for all groups available to the forum.
231      * @param forum_entity $forum The forum entity
232      * @return stdClass[]
233      */
234     private function get_groups_available_in_forum($forum) : array {
235         $course = $forum->get_course_record();
236         $coursemodule = $forum->get_course_module_record();
238         return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
239     }
241     /**
242      * Get the author's groups for a list of posts.
243      *
244      * @param post_entity[] $posts The list of posts
245      * @param forum_entity $forum The forum entity
246      * @return array Author groups indexed by author id
247      */
248     private function get_author_groups_from_posts(array $posts, $forum) : array {
249         $course = $forum->get_course_record();
250         $coursemodule = $forum->get_course_module_record();
251         $authorids = array_reduce($posts, function($carry, $post) {
252             $carry[$post->get_author_id()] = true;
253             return $carry;
254         }, []);
255         $authorgroups = groups_get_all_groups($course->id, array_keys($authorids), $coursemodule->groupingid,
256                 'g.*, gm.id, gm.groupid, gm.userid');
258         $authorgroups = array_reduce($authorgroups, function($carry, $group) {
259             // Clean up data returned from groups_get_all_groups.
260             $userid = $group->userid;
261             $groupid = $group->groupid;
263             unset($group->userid);
264             unset($group->groupid);
265             $group->id = $groupid;
267             if (!isset($carry[$userid])) {
268                 $carry[$userid] = [$group];
269             } else {
270                 $carry[$userid][] = $group;
271             }
273             return $carry;
274         }, []);
276         foreach (array_diff(array_keys($authorids), array_keys($authorgroups)) as $authorid) {
277             $authorgroups[$authorid] = [];
278         }
280         return $authorgroups;
281     }
283     /**
284      * Get the user context ids for each of the authors.
285      *
286      * @param int[] $authorids The list of author ids to fetch context ids for.
287      * @return int[] Context ids indexed by author id
288      */
289     private function get_author_context_ids(array $authorids) : array {
290         $authorvault = $this->vaultfactory->get_author_vault();
291         return $authorvault->get_context_ids_for_author_ids($authorids);
292     }