Mercurial > hg > isophonics-drupal-site
comparison core/modules/forum/src/ForumManager.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 129ea1e6d783 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\forum; | |
4 | |
5 use Drupal\Core\Config\ConfigFactoryInterface; | |
6 use Drupal\Core\Database\Connection; | |
7 use Drupal\Core\DependencyInjection\DependencySerializationTrait; | |
8 use Drupal\Core\Entity\EntityManagerInterface; | |
9 use Drupal\Core\Session\AccountInterface; | |
10 use Drupal\Core\StringTranslation\TranslationInterface; | |
11 use Drupal\Core\StringTranslation\StringTranslationTrait; | |
12 use Drupal\comment\CommentManagerInterface; | |
13 use Drupal\node\NodeInterface; | |
14 | |
15 /** | |
16 * Provides forum manager service. | |
17 */ | |
18 class ForumManager implements ForumManagerInterface { | |
19 use StringTranslationTrait; | |
20 use DependencySerializationTrait { | |
21 __wakeup as defaultWakeup; | |
22 __sleep as defaultSleep; | |
23 } | |
24 | |
25 /** | |
26 * Forum sort order, newest first. | |
27 */ | |
28 const NEWEST_FIRST = 1; | |
29 | |
30 /** | |
31 * Forum sort order, oldest first. | |
32 */ | |
33 const OLDEST_FIRST = 2; | |
34 | |
35 /** | |
36 * Forum sort order, posts with most comments first. | |
37 */ | |
38 const MOST_POPULAR_FIRST = 3; | |
39 | |
40 /** | |
41 * Forum sort order, posts with the least comments first. | |
42 */ | |
43 const LEAST_POPULAR_FIRST = 4; | |
44 | |
45 /** | |
46 * Forum settings config object. | |
47 * | |
48 * @var \Drupal\Core\Config\ConfigFactoryInterface | |
49 */ | |
50 protected $configFactory; | |
51 | |
52 /** | |
53 * Entity manager service | |
54 * | |
55 * @var \Drupal\Core\Entity\EntityManagerInterface | |
56 */ | |
57 protected $entityManager; | |
58 | |
59 /** | |
60 * Database connection | |
61 * | |
62 * @var \Drupal\Core\Database\Connection | |
63 */ | |
64 protected $connection; | |
65 | |
66 /** | |
67 * The comment manager service. | |
68 * | |
69 * @var \Drupal\comment\CommentManagerInterface | |
70 */ | |
71 protected $commentManager; | |
72 | |
73 /** | |
74 * Array of last post information keyed by forum (term) id. | |
75 * | |
76 * @var array | |
77 */ | |
78 protected $lastPostData = []; | |
79 | |
80 /** | |
81 * Array of forum statistics keyed by forum (term) id. | |
82 * | |
83 * @var array | |
84 */ | |
85 protected $forumStatistics = []; | |
86 | |
87 /** | |
88 * Array of forum children keyed by parent forum (term) id. | |
89 * | |
90 * @var array | |
91 */ | |
92 protected $forumChildren = []; | |
93 | |
94 /** | |
95 * Array of history keyed by nid. | |
96 * | |
97 * @var array | |
98 */ | |
99 protected $history = []; | |
100 | |
101 /** | |
102 * Cached forum index. | |
103 * | |
104 * @var \Drupal\taxonomy\TermInterface | |
105 */ | |
106 protected $index; | |
107 | |
108 /** | |
109 * Constructs the forum manager service. | |
110 * | |
111 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
112 * The config factory service. | |
113 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager | |
114 * The entity manager service. | |
115 * @param \Drupal\Core\Database\Connection $connection | |
116 * The current database connection. | |
117 * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation | |
118 * The translation manager service. | |
119 * @param \Drupal\comment\CommentManagerInterface $comment_manager | |
120 * The comment manager service. | |
121 */ | |
122 public function __construct(ConfigFactoryInterface $config_factory, EntityManagerInterface $entity_manager, Connection $connection, TranslationInterface $string_translation, CommentManagerInterface $comment_manager) { | |
123 $this->configFactory = $config_factory; | |
124 $this->entityManager = $entity_manager; | |
125 $this->connection = $connection; | |
126 $this->stringTranslation = $string_translation; | |
127 $this->commentManager = $comment_manager; | |
128 } | |
129 | |
130 /** | |
131 * {@inheritdoc} | |
132 */ | |
133 public function getTopics($tid, AccountInterface $account) { | |
134 $config = $this->configFactory->get('forum.settings'); | |
135 $forum_per_page = $config->get('topics.page_limit'); | |
136 $sortby = $config->get('topics.order'); | |
137 | |
138 $header = [ | |
139 ['data' => $this->t('Topic'), 'field' => 'f.title'], | |
140 ['data' => $this->t('Replies'), 'field' => 'f.comment_count'], | |
141 ['data' => $this->t('Last reply'), 'field' => 'f.last_comment_timestamp'], | |
142 ]; | |
143 | |
144 $order = $this->getTopicOrder($sortby); | |
145 for ($i = 0; $i < count($header); $i++) { | |
146 if ($header[$i]['field'] == $order['field']) { | |
147 $header[$i]['sort'] = $order['sort']; | |
148 } | |
149 } | |
150 | |
151 $query = $this->connection->select('forum_index', 'f') | |
152 ->extend('Drupal\Core\Database\Query\PagerSelectExtender') | |
153 ->extend('Drupal\Core\Database\Query\TableSortExtender'); | |
154 $query->fields('f'); | |
155 $query | |
156 ->condition('f.tid', $tid) | |
157 ->addTag('node_access') | |
158 ->addMetaData('base_table', 'forum_index') | |
159 ->orderBy('f.sticky', 'DESC') | |
160 ->orderByHeader($header) | |
161 ->limit($forum_per_page); | |
162 | |
163 $count_query = $this->connection->select('forum_index', 'f'); | |
164 $count_query->condition('f.tid', $tid); | |
165 $count_query->addExpression('COUNT(*)'); | |
166 $count_query->addTag('node_access'); | |
167 $count_query->addMetaData('base_table', 'forum_index'); | |
168 | |
169 $query->setCountQuery($count_query); | |
170 $result = $query->execute(); | |
171 $nids = []; | |
172 foreach ($result as $record) { | |
173 $nids[] = $record->nid; | |
174 } | |
175 if ($nids) { | |
176 $nodes = $this->entityManager->getStorage('node')->loadMultiple($nids); | |
177 | |
178 $query = $this->connection->select('node_field_data', 'n') | |
179 ->extend('Drupal\Core\Database\Query\TableSortExtender'); | |
180 $query->fields('n', ['nid']); | |
181 | |
182 $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'"); | |
183 $query->fields('ces', [ | |
184 'cid', | |
185 'last_comment_uid', | |
186 'last_comment_timestamp', | |
187 'comment_count' | |
188 ]); | |
189 | |
190 $query->join('forum_index', 'f', 'f.nid = n.nid'); | |
191 $query->addField('f', 'tid', 'forum_tid'); | |
192 | |
193 $query->join('users_field_data', 'u', 'n.uid = u.uid AND u.default_langcode = 1'); | |
194 $query->addField('u', 'name'); | |
195 | |
196 $query->join('users_field_data', 'u2', 'ces.last_comment_uid = u2.uid AND u.default_langcode = 1'); | |
197 | |
198 $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u2.name END', 'last_comment_name'); | |
199 | |
200 $query | |
201 ->orderBy('f.sticky', 'DESC') | |
202 ->orderByHeader($header) | |
203 ->condition('n.nid', $nids, 'IN') | |
204 // @todo This should be actually filtering on the desired node language | |
205 // and just fall back to the default language. | |
206 ->condition('n.default_langcode', 1); | |
207 | |
208 $result = []; | |
209 foreach ($query->execute() as $row) { | |
210 $topic = $nodes[$row->nid]; | |
211 $topic->comment_mode = $topic->comment_forum->status; | |
212 | |
213 foreach ($row as $key => $value) { | |
214 $topic->{$key} = $value; | |
215 } | |
216 $result[] = $topic; | |
217 } | |
218 } | |
219 else { | |
220 $result = []; | |
221 } | |
222 | |
223 $topics = []; | |
224 $first_new_found = FALSE; | |
225 foreach ($result as $topic) { | |
226 if ($account->isAuthenticated()) { | |
227 // A forum is new if the topic is new, or if there are new comments since | |
228 // the user's last visit. | |
229 if ($topic->forum_tid != $tid) { | |
230 $topic->new = 0; | |
231 } | |
232 else { | |
233 $history = $this->lastVisit($topic->id(), $account); | |
234 $topic->new_replies = $this->commentManager->getCountNewComments($topic, 'comment_forum', $history); | |
235 $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history); | |
236 } | |
237 } | |
238 else { | |
239 // Do not track "new replies" status for topics if the user is anonymous. | |
240 $topic->new_replies = 0; | |
241 $topic->new = 0; | |
242 } | |
243 | |
244 // Make sure only one topic is indicated as the first new topic. | |
245 $topic->first_new = FALSE; | |
246 if ($topic->new != 0 && !$first_new_found) { | |
247 $topic->first_new = TRUE; | |
248 $first_new_found = TRUE; | |
249 } | |
250 | |
251 if ($topic->comment_count > 0) { | |
252 $last_reply = new \stdClass(); | |
253 $last_reply->created = $topic->last_comment_timestamp; | |
254 $last_reply->name = $topic->last_comment_name; | |
255 $last_reply->uid = $topic->last_comment_uid; | |
256 $topic->last_reply = $last_reply; | |
257 } | |
258 $topics[$topic->id()] = $topic; | |
259 } | |
260 | |
261 return ['topics' => $topics, 'header' => $header]; | |
262 | |
263 } | |
264 | |
265 /** | |
266 * Gets topic sorting information based on an integer code. | |
267 * | |
268 * @param int $sortby | |
269 * One of the following integers indicating the sort criteria: | |
270 * - ForumManager::NEWEST_FIRST: Date - newest first. | |
271 * - ForumManager::OLDEST_FIRST: Date - oldest first. | |
272 * - ForumManager::MOST_POPULAR_FIRST: Posts with the most comments first. | |
273 * - ForumManager::LEAST_POPULAR_FIRST: Posts with the least comments first. | |
274 * | |
275 * @return array | |
276 * An array with the following values: | |
277 * - field: A field for an SQL query. | |
278 * - sort: 'asc' or 'desc'. | |
279 */ | |
280 protected function getTopicOrder($sortby) { | |
281 switch ($sortby) { | |
282 case static::NEWEST_FIRST: | |
283 return ['field' => 'f.last_comment_timestamp', 'sort' => 'desc']; | |
284 | |
285 case static::OLDEST_FIRST: | |
286 return ['field' => 'f.last_comment_timestamp', 'sort' => 'asc']; | |
287 | |
288 case static::MOST_POPULAR_FIRST: | |
289 return ['field' => 'f.comment_count', 'sort' => 'desc']; | |
290 | |
291 case static::LEAST_POPULAR_FIRST: | |
292 return ['field' => 'f.comment_count', 'sort' => 'asc']; | |
293 | |
294 } | |
295 } | |
296 | |
297 /** | |
298 * Gets the last time the user viewed a node. | |
299 * | |
300 * @param int $nid | |
301 * The node ID. | |
302 * @param \Drupal\Core\Session\AccountInterface $account | |
303 * Account to fetch last time for. | |
304 * | |
305 * @return int | |
306 * The timestamp when the user last viewed this node, if the user has | |
307 * previously viewed the node; otherwise HISTORY_READ_LIMIT. | |
308 */ | |
309 protected function lastVisit($nid, AccountInterface $account) { | |
310 if (empty($this->history[$nid])) { | |
311 $result = $this->connection->select('history', 'h') | |
312 ->fields('h', ['nid', 'timestamp']) | |
313 ->condition('uid', $account->id()) | |
314 ->execute(); | |
315 foreach ($result as $t) { | |
316 $this->history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT; | |
317 } | |
318 } | |
319 return isset($this->history[$nid]) ? $this->history[$nid] : HISTORY_READ_LIMIT; | |
320 } | |
321 | |
322 /** | |
323 * Provides the last post information for the given forum tid. | |
324 * | |
325 * @param int $tid | |
326 * The forum tid. | |
327 * | |
328 * @return \stdClass | |
329 * The last post for the given forum. | |
330 */ | |
331 protected function getLastPost($tid) { | |
332 if (!empty($this->lastPostData[$tid])) { | |
333 return $this->lastPostData[$tid]; | |
334 } | |
335 // Query "Last Post" information for this forum. | |
336 $query = $this->connection->select('node_field_data', 'n'); | |
337 $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', [':tid' => $tid]); | |
338 $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'"); | |
339 $query->join('users_field_data', 'u', 'ces.last_comment_uid = u.uid AND u.default_langcode = 1'); | |
340 $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u.name END', 'last_comment_name'); | |
341 | |
342 $topic = $query | |
343 ->fields('ces', ['last_comment_timestamp', 'last_comment_uid']) | |
344 ->condition('n.status', 1) | |
345 ->orderBy('last_comment_timestamp', 'DESC') | |
346 ->range(0, 1) | |
347 ->addTag('node_access') | |
348 ->execute() | |
349 ->fetchObject(); | |
350 | |
351 // Build the last post information. | |
352 $last_post = new \stdClass(); | |
353 if (!empty($topic->last_comment_timestamp)) { | |
354 $last_post->created = $topic->last_comment_timestamp; | |
355 $last_post->name = $topic->last_comment_name; | |
356 $last_post->uid = $topic->last_comment_uid; | |
357 } | |
358 | |
359 $this->lastPostData[$tid] = $last_post; | |
360 return $last_post; | |
361 } | |
362 | |
363 /** | |
364 * Provides statistics for a forum. | |
365 * | |
366 * @param int $tid | |
367 * The forum tid. | |
368 * | |
369 * @return \stdClass|null | |
370 * Statistics for the given forum if statistics exist, else NULL. | |
371 */ | |
372 protected function getForumStatistics($tid) { | |
373 if (empty($this->forumStatistics)) { | |
374 // Prime the statistics. | |
375 $query = $this->connection->select('node_field_data', 'n'); | |
376 $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'"); | |
377 $query->join('forum', 'f', 'n.vid = f.vid'); | |
378 $query->addExpression('COUNT(n.nid)', 'topic_count'); | |
379 $query->addExpression('SUM(ces.comment_count)', 'comment_count'); | |
380 $this->forumStatistics = $query | |
381 ->fields('f', ['tid']) | |
382 ->condition('n.status', 1) | |
383 ->condition('n.default_langcode', 1) | |
384 ->groupBy('tid') | |
385 ->addTag('node_access') | |
386 ->execute() | |
387 ->fetchAllAssoc('tid'); | |
388 } | |
389 | |
390 if (!empty($this->forumStatistics[$tid])) { | |
391 return $this->forumStatistics[$tid]; | |
392 } | |
393 } | |
394 | |
395 /** | |
396 * {@inheritdoc} | |
397 */ | |
398 public function getChildren($vid, $tid) { | |
399 if (!empty($this->forumChildren[$tid])) { | |
400 return $this->forumChildren[$tid]; | |
401 } | |
402 $forums = []; | |
403 $_forums = $this->entityManager->getStorage('taxonomy_term')->loadTree($vid, $tid, NULL, TRUE); | |
404 foreach ($_forums as $forum) { | |
405 // Merge in the topic and post counters. | |
406 if (($count = $this->getForumStatistics($forum->id()))) { | |
407 $forum->num_topics = $count->topic_count; | |
408 $forum->num_posts = $count->topic_count + $count->comment_count; | |
409 } | |
410 else { | |
411 $forum->num_topics = 0; | |
412 $forum->num_posts = 0; | |
413 } | |
414 | |
415 // Merge in last post details. | |
416 $forum->last_post = $this->getLastPost($forum->id()); | |
417 $forums[$forum->id()] = $forum; | |
418 } | |
419 | |
420 $this->forumChildren[$tid] = $forums; | |
421 return $forums; | |
422 } | |
423 | |
424 /** | |
425 * {@inheritdoc} | |
426 */ | |
427 public function getIndex() { | |
428 if ($this->index) { | |
429 return $this->index; | |
430 } | |
431 | |
432 $vid = $this->configFactory->get('forum.settings')->get('vocabulary'); | |
433 $index = $this->entityManager->getStorage('taxonomy_term')->create([ | |
434 'tid' => 0, | |
435 'container' => 1, | |
436 'parents' => [], | |
437 'isIndex' => TRUE, | |
438 'vid' => $vid | |
439 ]); | |
440 | |
441 // Load the tree below. | |
442 $index->forums = $this->getChildren($vid, 0); | |
443 $this->index = $index; | |
444 return $index; | |
445 } | |
446 | |
447 /** | |
448 * {@inheritdoc} | |
449 */ | |
450 public function resetCache() { | |
451 // Reset the index. | |
452 $this->index = NULL; | |
453 // Reset history. | |
454 $this->history = []; | |
455 } | |
456 | |
457 /** | |
458 * {@inheritdoc} | |
459 */ | |
460 public function getParents($tid) { | |
461 return $this->entityManager->getStorage('taxonomy_term')->loadAllParents($tid); | |
462 } | |
463 | |
464 /** | |
465 * {@inheritdoc} | |
466 */ | |
467 public function checkNodeType(NodeInterface $node) { | |
468 // Fetch information about the forum field. | |
469 $field_definitions = $this->entityManager->getFieldDefinitions('node', $node->bundle()); | |
470 return !empty($field_definitions['taxonomy_forums']); | |
471 } | |
472 | |
473 /** | |
474 * {@inheritdoc} | |
475 */ | |
476 public function unreadTopics($term, $uid) { | |
477 $query = $this->connection->select('node_field_data', 'n'); | |
478 $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', [':tid' => $term]); | |
479 $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', [':uid' => $uid]); | |
480 $query->addExpression('COUNT(n.nid)', 'count'); | |
481 return $query | |
482 ->condition('status', 1) | |
483 // @todo This should be actually filtering on the desired node status | |
484 // field language and just fall back to the default language. | |
485 ->condition('n.default_langcode', 1) | |
486 ->condition('n.created', HISTORY_READ_LIMIT, '>') | |
487 ->isNull('h.nid') | |
488 ->addTag('node_access') | |
489 ->execute() | |
490 ->fetchField(); | |
491 } | |
492 | |
493 /** | |
494 * {@inheritdoc} | |
495 */ | |
496 public function __sleep() { | |
497 $vars = $this->defaultSleep(); | |
498 // Do not serialize static cache. | |
499 unset($vars['history'], $vars['index'], $vars['lastPostData'], $vars['forumChildren'], $vars['forumStatistics']); | |
500 return $vars; | |
501 } | |
502 | |
503 /** | |
504 * {@inheritdoc} | |
505 */ | |
506 public function __wakeup() { | |
507 $this->defaultWakeup(); | |
508 // Initialize static cache. | |
509 $this->history = []; | |
510 $this->lastPostData = []; | |
511 $this->forumChildren = []; | |
512 $this->forumStatistics = []; | |
513 $this->index = NULL; | |
514 } | |
515 | |
516 } |