MDL-66481 mod_forum: Implement additional column sorting
[moodle.git] / mod / forum / classes / local / builders / exported_discussion_summaries.php
CommitLineData
54d38a73
MG
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 * 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 */
24
25namespace mod_forum\local\builders;
26
27defined('MOODLE_INTERNAL') || die();
28
29use mod_forum\local\entities\discussion as discussion_entity;
30use mod_forum\local\entities\forum as forum_entity;
54d38a73
MG
31use mod_forum\local\entities\post as post_entity;
32use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
33use mod_forum\local\factories\exporter as exporter_factory;
34use mod_forum\local\factories\vault as vault_factory;
bc4c7337 35use mod_forum\local\factories\manager as manager_factory;
54d38a73
MG
36use rating_manager;
37use renderer_base;
38use stdClass;
39
40/**
446ba046
RW
41 * Exported discussion summaries builder class.
42 *
54d38a73
MG
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
446ba046
RW
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
54d38a73
MG
57 */
58class exported_discussion_summaries {
59 /** @var renderer_base $renderer Core renderer */
60 private $renderer;
61
62 /** @var legacy_data_mapper_factory $legacydatamapperfactory Data mapper factory */
63 private $legacydatamapperfactory;
64
65 /** @var exporter_factory $exporterfactory Exporter factory */
66 private $exporterfactory;
67
68 /** @var vault_factory $vaultfactory Vault factory */
69 private $vaultfactory;
70
bc4c7337
AN
71 /** @var manager_factory $managerfactory Manager factory */
72 private $managerfactory;
73
54d38a73
MG
74 /** @var rating_manager $ratingmanager Rating manager */
75 private $ratingmanager;
76
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
8245daba 84 * @param manager_factory $managerfactory Manager factory
54d38a73
MG
85 */
86 public function __construct(
87 renderer_base $renderer,
88 legacy_data_mapper_factory $legacydatamapperfactory,
89 exporter_factory $exporterfactory,
90 vault_factory $vaultfactory,
8245daba 91 manager_factory $managerfactory
54d38a73
MG
92 ) {
93 $this->renderer = $renderer;
94 $this->legacydatamapperfactory = $legacydatamapperfactory;
95 $this->exporterfactory = $exporterfactory;
96 $this->vaultfactory = $vaultfactory;
bc4c7337 97 $this->managerfactory = $managerfactory;
8245daba 98 $this->ratingmanager = $managerfactory->get_rating_manager();
54d38a73
MG
99 }
100
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
83537707 108 * @param discussion_summary_entity[] $discussions A list of all discussion summaries to export
54d38a73
MG
109 * @return stdClass[] List of exported posts in the same order as the $posts array.
110 */
111 public function build(
112 stdClass $user,
446ba046 113 forum_entity $forum,
54d38a73
MG
114 array $discussions
115 ) : array {
bc4c7337
AN
116 $capabilitymanager = $this->managerfactory->get_capability_manager($forum);
117 $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
54d38a73
MG
118
119 $discussionids = array_keys($discussions);
120
121 $postvault = $this->vaultfactory->get_post_vault();
bc4c7337 122 $posts = $postvault->get_from_discussion_ids($user, $discussionids, $canseeanyprivatereply);
54d38a73
MG
123 $groupsbyid = $this->get_groups_available_in_forum($forum);
124 $groupsbyauthorid = $this->get_author_groups_from_posts($posts, $forum);
125
bc4c7337 126 $replycounts = $postvault->get_reply_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
9b8e5998
P
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);
132
133 $postauthorids = array_unique(array_reduce($discussions, function($carry, $summary) use ($latestposts){
83537707 134 $firstpostauthorid = $summary->get_first_post_author()->get_id();
9b8e5998
P
135 $discussion = $summary->get_discussion();
136 $lastpostauthorid = $latestposts[$discussion->get_id()]->get_author_id();
83537707
RW
137 return array_merge($carry, [$firstpostauthorid, $lastpostauthorid]);
138 }, []));
139 $postauthorcontextids = $this->get_author_context_ids($postauthorids);
54d38a73
MG
140
141 $unreadcounts = [];
13cd05ac 142 $favourites = $this->get_favourites($user);
54d38a73
MG
143 $forumdatamapper = $this->legacydatamapperfactory->get_forum_data_mapper();
144 $forumrecord = $forumdatamapper->to_legacy_object($forum);
145
146 if (forum_tp_can_track_forums($forumrecord)) {
bc4c7337 147 $unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
54d38a73
MG
148 }
149
150 $summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
151 $user,
152 $forum,
153 $discussions,
154 $groupsbyid,
155 $groupsbyauthorid,
156 $replycounts,
157 $unreadcounts,
9b8e5998 158 $latestpostsids,
83537707 159 $postauthorcontextids,
9b8e5998
P
160 $favourites,
161 $latestauthors
54d38a73
MG
162 );
163
1a9c60e9
MG
164 $exportedposts = (array) $summaryexporter->export($this->renderer);
165 $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
166
9b8e5998 167 array_walk($exportedposts['summaries'], function($summary) use ($firstposts, $latestposts) {
f31c531c 168 $summary->discussion->times['created'] = (int) $firstposts[$summary->discussion->firstpostid]->get_time_created();
9b8e5998 169 $summary->discussion->times['modified'] = (int) $latestposts[$summary->discussion->id]->get_time_created();
1a9c60e9
MG
170 });
171
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);
176
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,
bc23cbaf
JP
183 'iscreatedasc' => $sortorder == $discussionlistvault::SORTORDER_CREATED_ASC,
184 'isdiscussiondesc' => $sortorder == $discussionlistvault::SORTORDER_DISCUSSION_DESC,
185 'isdiscussionasc' => $sortorder == $discussionlistvault::SORTORDER_DISCUSSION_ASC,
186 'isstarterdesc' => $sortorder == $discussionlistvault::SORTORDER_STARTER_DESC,
187 'isstarterasc' => $sortorder == $discussionlistvault::SORTORDER_STARTER_ASC,
188 'isgroupdesc' => $sortorder == $discussionlistvault::SORTORDER_GROUP_DESC,
189 'isgroupasc' => $sortorder == $discussionlistvault::SORTORDER_GROUP_ASC,
1a9c60e9
MG
190 );
191
192 $exportedposts['state']['sortorder'] = $sortoptions;
193
194 return $exportedposts;
54d38a73
MG
195 }
196
13cd05ac
P
197 /**
198 * Get a list of all favourited discussions.
199 *
200 * @param stdClass $user The user we are getting favourites for
201 * @return int[] A list of favourited itemids
202 */
203 private function get_favourites(stdClass $user) : array {
13cd05ac 204 $ids = [];
d3cac88d
P
205
206 if (isloggedin()) {
207 $usercontext = \context_user::instance($user->id);
208 $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
209 $favourites = $ufservice->find_favourites_by_type('mod_forum', 'discussions');
210 foreach ($favourites as $favourite) {
211 $ids[] = $favourite->itemid;
212 }
13cd05ac
P
213 }
214
215 return $ids;
216 }
217
9b8e5998
P
218 /**
219 * Returns a mapped array of discussionid to the authors of the latest post
220 *
221 * @param array $latestposts Mapped array of discussion to latest posts.
222 * @return array Array of authors mapped to the discussion
223 */
224 private function get_latest_posts_authors($latestposts) {
225 $authors = $this->vaultfactory->get_author_vault()->get_authors_for_posts($latestposts);
226
227 $mappedauthors = array_reduce($latestposts, function($carry, $item) use ($authors) {
228 $carry[$item->get_discussion_id()] = $authors[$item->get_author_id()];
229
230 return $carry;
231 }, []);
232 return $mappedauthors;
233 }
234
54d38a73
MG
235 /**
236 * Get the groups details for all groups available to the forum.
237 * @param forum_entity $forum The forum entity
238 * @return stdClass[]
239 */
240 private function get_groups_available_in_forum($forum) : array {
241 $course = $forum->get_course_record();
242 $coursemodule = $forum->get_course_module_record();
243
244 return groups_get_all_groups($course->id, 0, $coursemodule->groupingid);
245 }
246
247 /**
248 * Get the author's groups for a list of posts.
249 *
250 * @param post_entity[] $posts The list of posts
251 * @param forum_entity $forum The forum entity
252 * @return array Author groups indexed by author id
253 */
254 private function get_author_groups_from_posts(array $posts, $forum) : array {
255 $course = $forum->get_course_record();
256 $coursemodule = $forum->get_course_module_record();
257 $authorids = array_reduce($posts, function($carry, $post) {
258 $carry[$post->get_author_id()] = true;
259 return $carry;
260 }, []);
261 $authorgroups = groups_get_all_groups($course->id, array_keys($authorids), $coursemodule->groupingid,
262 'g.*, gm.id, gm.groupid, gm.userid');
263
264 $authorgroups = array_reduce($authorgroups, function($carry, $group) {
265 // Clean up data returned from groups_get_all_groups.
266 $userid = $group->userid;
267 $groupid = $group->groupid;
268
269 unset($group->userid);
270 unset($group->groupid);
271 $group->id = $groupid;
272
273 if (!isset($carry[$userid])) {
274 $carry[$userid] = [$group];
275 } else {
276 $carry[$userid][] = $group;
277 }
278
279 return $carry;
280 }, []);
281
282 foreach (array_diff(array_keys($authorids), array_keys($authorgroups)) as $authorid) {
283 $authorgroups[$authorid] = [];
284 }
285
286 return $authorgroups;
287 }
83537707
RW
288
289 /**
290 * Get the user context ids for each of the authors.
291 *
292 * @param int[] $authorids The list of author ids to fetch context ids for.
293 * @return int[] Context ids indexed by author id
294 */
295 private function get_author_context_ids(array $authorids) : array {
296 $authorvault = $this->vaultfactory->get_author_vault();
297 return $authorvault->get_context_ids_for_author_ids($authorids);
298 }
54d38a73 299}