Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / forum / forum.module
1 <?php
2
3 /**
4  * @file
5  * Provides discussion forums.
6  */
7
8 use Drupal\comment\CommentInterface;
9 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
10 use Drupal\Core\Entity\EntityInterface;
11 use Drupal\Core\Entity\EntityTypeInterface;
12 use Drupal\Core\Url;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Routing\RouteMatchInterface;
15 use Drupal\taxonomy\VocabularyInterface;
16 use Drupal\user\Entity\User;
17
18 /**
19  * Implements hook_help().
20  */
21 function forum_help($route_name, RouteMatchInterface $route_match) {
22   switch ($route_name) {
23     case 'help.page.forum':
24       $output = '';
25       $output .= '<h3>' . t('About') . '</h3>';
26       $output .= '<p>' . t('The Forum module lets you create threaded discussion forums with functionality similar to other message board systems. In a forum, users post topics and threads in nested hierarchies, allowing discussions to be categorized and grouped.') . '</p>';
27       $output .= '<p>' . t('The Forum module adds and uses a content type called <em>Forum topic</em>. For background information on content types, see the <a href=":node_help">Node module help page</a>.', [':node_help' => \Drupal::url('help.page', ['name' => 'node'])]) . '</p>';
28       $output .= '<p>' . t('A forum is represented by a hierarchical structure, consisting of:');
29       $output .= '<ul>';
30       $output .= '<li>' . t('<em>Forums</em> (for example, <em>Recipes for cooking vegetables</em>)') . '</li>';
31       $output .= '<li>' . t('<em>Forum topics</em> submitted by users (for example, <em>How to cook potatoes</em>), which start discussions.') . '</li>';
32       $output .= '<li>' . t('Threaded <em>comments</em> submitted by users (for example, <em>You wash the potatoes first and then...</em>).') . '</li>';
33       $output .= '<li>' . t('Optional <em>containers</em>, used to group similar forums. Forums can be placed inside containers, and vice versa.') . '</li>';
34       $output .= '</ul>';
35       $output .= '</p>';
36       $output .= '<p>' . t('For more information, see the <a href=":forum">online documentation for the Forum module</a>.', [':forum' => 'https://www.drupal.org/documentation/modules/forum']) . '</p>';
37       $output .= '<h3>' . t('Uses') . '</h3>';
38       $output .= '<dl>';
39       $output .= '<dt>' . t('Setting up the forum structure') . '</dt>';
40       $output .= '<dd>' . t('Visit the <a href=":forums">Forums page</a> to set up containers and forums to hold your discussion topics.', [':forums' => \Drupal::url('forum.overview')]) . '</dd>';
41       $output .= '<dt>' . t('Starting a discussion') . '</dt>';
42       $output .= '<dd>' . t('The <a href=":create-topic">Forum topic</a> link on the <a href=":content-add">Add content</a> page creates the first post of a new threaded discussion, or thread.', [':create-topic' => \Drupal::url('node.add', ['node_type' => 'forum']), ':content-add' => \Drupal::url('node.add_page')]) . '</dd>';
43       $output .= '<dt>' . t('Navigating in the forum') . '</dt>';
44       $output .= '<dd>' . t('Enabling the Forum module provides a default <em>Forums</em> menu item in the Tools menu that links to the <a href=":forums">Forums page</a>.', [':forums' => \Drupal::url('forum.index')]) . '</dd>';
45       $output .= '<dt>' . t('Moving forum topics') . '</dt>';
46       $output .= '<dd>' . t('A forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic. When moving a forum topic between forums, the <em>Leave shadow copy</em> option creates a link in the original forum pointing to the new location.') . '</dd>';
47       $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
48       $output .= '<dd>' . t('Selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments on) the thread. Selecting <em>Hidden</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') . '</dd>';
49       $output .= '</dl>';
50       return $output;
51
52     case 'forum.overview':
53       $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
54       $more_help_link = [
55         '#type' => 'link',
56         '#url' => Url::fromRoute('help.page', ['name' => 'forum']),
57         '#title' => t('More help'),
58         '#attributes' => [
59           'class' => ['icon-help'],
60         ],
61       ];
62       $container = [
63         '#theme' => 'container',
64         '#children' => $more_help_link,
65         '#attributes' => [
66           'class' => ['more-link'],
67         ],
68       ];
69       $output .= \Drupal::service('renderer')->renderPlain($container);
70       return $output;
71
72     case 'forum.add_container':
73       return '<p>' . t('Use containers to group related forums.') . '</p>';
74
75     case 'forum.add_forum':
76       return '<p>' . t('A forum holds related forum topics.') . '</p>';
77
78     case 'forum.settings':
79       return '<p>' . t('Adjust the display of your forum topics. Organize the forums on the <a href=":forum-structure">forum structure page</a>.', [':forum-structure' => \Drupal::url('forum.overview')]) . '</p>';
80   }
81 }
82
83 /**
84  * Implements hook_theme().
85  */
86 function forum_theme() {
87   return [
88     'forums' => [
89       'variables' => ['forums' => [], 'topics' => [], 'topics_pager' => [], 'parents' => NULL, 'term' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL, 'header' => []],
90     ],
91     'forum_list' => [
92       'variables' => ['forums' => NULL, 'parents' => NULL, 'tid' => NULL],
93     ],
94     'forum_icon' => [
95       'variables' => ['new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE],
96     ],
97     'forum_submitted' => [
98       'variables' => ['topic' => NULL],
99     ],
100   ];
101 }
102
103 /**
104  * Implements hook_entity_type_build().
105  */
106 function forum_entity_type_build(array &$entity_types) {
107   /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
108   // Register forum specific forms.
109   $entity_types['taxonomy_term']
110     ->setFormClass('forum', 'Drupal\forum\Form\ForumForm')
111     ->setFormClass('container', 'Drupal\forum\Form\ContainerForm')
112     ->setLinkTemplate('forum-edit-container-form', '/admin/structure/forum/edit/container/{taxonomy_term}')
113     ->setLinkTemplate('forum-delete-form', '/admin/structure/forum/delete/forum/{taxonomy_term}')
114     ->setLinkTemplate('forum-edit-form', '/admin/structure/forum/edit/forum/{taxonomy_term}');
115 }
116
117 /**
118  * Implements hook_entity_bundle_info_alter().
119  */
120 function forum_entity_bundle_info_alter(&$bundles) {
121   // Take over URI construction for taxonomy terms that are forums.
122   if ($vid = \Drupal::config('forum.settings')->get('vocabulary')) {
123     if (isset($bundles['taxonomy_term'][$vid])) {
124       $bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
125     }
126   }
127 }
128
129 /**
130  * Entity URI callback used in forum_entity_bundle_info_alter().
131  */
132 function forum_uri($forum) {
133   return Url::fromRoute('forum.page', ['taxonomy_term' => $forum->id()]);
134 }
135
136 /**
137  * Implements hook_entity_bundle_field_info_alter().
138  */
139 function forum_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
140   if ($entity_type->id() == 'node'  && !empty($fields['taxonomy_forums'])) {
141     $fields['taxonomy_forums']->addConstraint('ForumLeaf', []);
142   }
143 }
144
145 /**
146  * Implements hook_ENTITY_TYPE_presave() for node entities.
147  *
148  * Assigns the forum taxonomy when adding a topic from within a forum.
149  */
150 function forum_node_presave(EntityInterface $node) {
151   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
152     // Make sure all fields are set properly:
153     $node->icon = !empty($node->icon) ? $node->icon : '';
154     if (!$node->taxonomy_forums->isEmpty()) {
155       $node->forum_tid = $node->taxonomy_forums->target_id;
156       // Only do a shadow copy check if this is not a new node.
157       if (!$node->isNew()) {
158         $old_tid = \Drupal::service('forum.index_storage')->getOriginalTermId($node);
159         if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
160           // A shadow copy needs to be created. Retain new term and add old term.
161           $node->taxonomy_forums[count($node->taxonomy_forums)] = ['target_id' => $old_tid];
162         }
163       }
164     }
165   }
166 }
167
168 /**
169  * Implements hook_ENTITY_TYPE_update() for node entities.
170  */
171 function forum_node_update(EntityInterface $node) {
172   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
173     // If this is not a new revision and does exist, update the forum record,
174     // otherwise insert a new one.
175     /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
176     $forum_index_storage = \Drupal::service('forum.index_storage');
177     if ($node->getRevisionId() == $node->original->getRevisionId() && $forum_index_storage->getOriginalTermId($node)) {
178       if (!empty($node->forum_tid)) {
179         $forum_index_storage->update($node);
180       }
181       // The node is removed from the forum.
182       else {
183         $forum_index_storage->delete($node);
184       }
185     }
186     else {
187       if (!empty($node->forum_tid)) {
188         $forum_index_storage->create($node);
189       }
190     }
191     // If the node has a shadow forum topic, update the record for this
192     // revision.
193     if (!empty($node->shadow)) {
194       $forum_index_storage->deleteRevision($node);
195       $forum_index_storage->create($node);
196     }
197
198     // If the node is published, update the forum index.
199     if ($node->isPublished()) {
200       $forum_index_storage->deleteIndex($node);
201       $forum_index_storage->createIndex($node);
202     }
203     // When a forum node is unpublished, remove it from the forum_index table.
204     else {
205       $forum_index_storage->deleteIndex($node);
206     }
207   }
208 }
209
210 /**
211  * Implements hook_ENTITY_TYPE_insert() for node entities.
212  */
213 function forum_node_insert(EntityInterface $node) {
214   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
215     /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
216     $forum_index_storage = \Drupal::service('forum.index_storage');
217     if (!empty($node->forum_tid)) {
218       $forum_index_storage->create($node);
219     }
220
221     // If the node is published, update the forum index.
222     if ($node->isPublished()) {
223       $forum_index_storage->createIndex($node);
224     }
225   }
226 }
227
228 /**
229  * Implements hook_ENTITY_TYPE_predelete() for node entities.
230  */
231 function forum_node_predelete(EntityInterface $node) {
232   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
233     /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
234     $forum_index_storage = \Drupal::service('forum.index_storage');
235     $forum_index_storage->delete($node);
236     $forum_index_storage->deleteIndex($node);
237   }
238 }
239
240 /**
241  * Implements hook_ENTITY_TYPE_storage_load() for node entities.
242  */
243 function forum_node_storage_load($nodes) {
244   $node_vids = [];
245   foreach ($nodes as $node) {
246     if (\Drupal::service('forum_manager')->checkNodeType($node)) {
247       $node_vids[] = $node->getRevisionId();
248     }
249   }
250   if (!empty($node_vids)) {
251     $result = \Drupal::service('forum.index_storage')->read($node_vids);
252     foreach ($result as $record) {
253       $nodes[$record->nid]->forum_tid = $record->tid;
254     }
255   }
256 }
257
258 /**
259  * Implements hook_ENTITY_TYPE_update() for comment entities.
260  */
261 function forum_comment_update(CommentInterface $comment) {
262   if ($comment->getCommentedEntityTypeId() == 'node') {
263     \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
264   }
265 }
266
267 /**
268  * Implements hook_ENTITY_TYPE_insert() for comment entities.
269  */
270 function forum_comment_insert(CommentInterface $comment) {
271   if ($comment->getCommentedEntityTypeId() == 'node') {
272     \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
273   }
274 }
275
276 /**
277  * Implements hook_ENTITY_TYPE_delete() for comment entities.
278  */
279 function forum_comment_delete(CommentInterface $comment) {
280   if ($comment->getCommentedEntityTypeId() == 'node') {
281     \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
282   }
283 }
284
285 /**
286  * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\taxonomy\VocabularyForm.
287  */
288 function forum_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $form_state, $form_id) {
289   $vid = \Drupal::config('forum.settings')->get('vocabulary');
290   $vocabulary = $form_state->getFormObject()->getEntity();
291   if ($vid == $vocabulary->id()) {
292     $form['help_forum_vocab'] = [
293       '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
294       '#weight' => -1,
295     ];
296     // Forum's vocabulary always has single hierarchy. Forums and containers
297     // have only one parent or no parent for root items. By default this value
298     // is 0.
299     $form['hierarchy']['#value'] = VocabularyInterface::HIERARCHY_SINGLE;
300     // Do not allow to delete forum's vocabulary.
301     $form['actions']['delete']['#access'] = FALSE;
302     // Do not allow to change a vid of forum's vocabulary.
303     $form['vid']['#disabled'] = TRUE;
304   }
305 }
306
307 /**
308  * Implements hook_form_FORM_ID_alter() for \Drupal\taxonomy\TermForm.
309  */
310 function forum_form_taxonomy_term_form_alter(&$form, FormStateInterface $form_state, $form_id) {
311   $vid = \Drupal::config('forum.settings')->get('vocabulary');
312   if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
313     // Hide multiple parents select from forum terms.
314     $form['relations']['parent']['#access'] = FALSE;
315   }
316 }
317
318 /**
319  * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
320  */
321 function forum_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
322   $node = $form_state->getFormObject()->getEntity();
323   if (isset($node->taxonomy_forums) && !$node->isNew()) {
324     $forum_terms = $node->taxonomy_forums;
325     // If editing, give option to leave shadows.
326     $shadow = (count($forum_terms) > 1);
327     $form['shadow'] = [
328       '#type' => 'checkbox',
329       '#title' => t('Leave shadow copy'),
330       '#default_value' => $shadow,
331       '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'),
332     ];
333     $form['forum_tid'] = ['#type' => 'value', '#value' => $node->forum_tid];
334   }
335
336   if (isset($form['taxonomy_forums'])) {
337     $widget =& $form['taxonomy_forums']['widget'];
338     // Make the vocabulary required for 'real' forum-nodes.
339     $widget['#required'] = TRUE;
340     $widget['#multiple'] = FALSE;
341     if (empty($widget['#default_value'])) {
342       // If there is no default forum already selected, try to get the forum
343       // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
344       // expect "2" to be the ID of the forum that was requested).
345       $requested_forum_id = \Drupal::request()->query->get('forum_id');
346       $widget['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
347     }
348   }
349 }
350
351 /**
352  * Implements hook_preprocess_HOOK() for block templates.
353  */
354 function forum_preprocess_block(&$variables) {
355   if ($variables['configuration']['provider'] == 'forum') {
356     $variables['attributes']['role'] = 'navigation';
357   }
358 }
359
360 /**
361  * Implements hook_theme_suggestions_HOOK().
362  */
363 function forum_theme_suggestions_forums(array $variables) {
364   $suggestions = [];
365   $tid = $variables['term']->id();
366
367   // Provide separate template suggestions based on what's being output. Topic
368   // ID is also accounted for. Check both variables to be safe then the inverse.
369   // Forums with topic IDs take precedence.
370   if ($variables['forums'] && !$variables['topics']) {
371     $suggestions[] = 'forums__containers';
372     $suggestions[] = 'forums__' . $tid;
373     $suggestions[] = 'forums__containers__' . $tid;
374   }
375   elseif (!$variables['forums'] && $variables['topics']) {
376     $suggestions[] = 'forums__topics';
377     $suggestions[] = 'forums__' . $tid;
378     $suggestions[] = 'forums__topics__' . $tid;
379   }
380   else {
381     $suggestions[] = 'forums__' . $tid;
382   }
383
384   return $suggestions;
385 }
386
387 /**
388  * Prepares variables for forums templates.
389  *
390  * Default template: forums.html.twig.
391  *
392  * @param array $variables
393  *   An array containing the following elements:
394  *   - forums: An array of all forum objects to display for the given taxonomy
395  *     term ID. If tid = 0 then all the top-level forums are displayed.
396  *   - topics: An array of all the topics in the current forum.
397  *   - parents: An array of taxonomy term objects that are ancestors of the
398  *     current term ID.
399  *   - term: Taxonomy term of the current forum.
400  *   - sortby: One of the following integers indicating the sort criteria:
401  *     - 1: Date - newest first.
402  *     - 2: Date - oldest first.
403  *     - 3: Posts with the most comments first.
404  *     - 4: Posts with the least comments first.
405  *   - forum_per_page: The maximum number of topics to display per page.
406  */
407 function template_preprocess_forums(&$variables) {
408   $variables['tid'] = $variables['term']->id();
409   if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
410     if (!empty($variables['forums'])) {
411       $variables['forums'] = [
412         '#theme' => 'forum_list',
413         '#forums' => $variables['forums'],
414         '#parents' => $variables['parents'],
415         '#tid' => $variables['tid'],
416       ];
417     }
418
419     if ($variables['term'] && empty($variables['term']->forum_container->value) && !empty($variables['topics'])) {
420       $forum_topic_list_header = $variables['header'];
421
422       $table = [
423         '#theme' => 'table__forum_topic_list',
424         '#responsive' => FALSE,
425         '#attributes' => ['id' => 'forum-topic-' . $variables['tid']],
426         '#header' => [],
427         '#rows' => [],
428       ];
429
430       if (!empty($forum_topic_list_header)) {
431         $table['#header'] = $forum_topic_list_header;
432       }
433
434       /** @var \Drupal\node\NodeInterface $topic */
435       foreach ($variables['topics'] as $id => $topic) {
436         $variables['topics'][$id]->icon = [
437           '#theme' => 'forum_icon',
438           '#new_posts' => $topic->new,
439           '#num_posts' => $topic->comment_count,
440           '#comment_mode' => $topic->comment_mode,
441           '#sticky' => $topic->isSticky(),
442           '#first_new' => $topic->first_new,
443         ];
444
445         // We keep the actual tid in forum table, if it's different from the
446         // current tid then it means the topic appears in two forums, one of
447         // them is a shadow copy.
448         if ($variables['tid'] != $topic->forum_tid) {
449           $variables['topics'][$id]->moved = TRUE;
450           $variables['topics'][$id]->title = $topic->getTitle();
451           $variables['topics'][$id]->message = \Drupal::l(t('This topic has been moved'), new Url('forum.page', ['taxonomy_term' => $topic->forum_tid]));
452         }
453         else {
454           $variables['topics'][$id]->moved = FALSE;
455           $variables['topics'][$id]->title_link = \Drupal::l($topic->getTitle(), $topic->urlInfo());
456           $variables['topics'][$id]->message = '';
457         }
458         $forum_submitted = ['#theme' => 'forum_submitted', '#topic' => (object) [
459           'uid' => $topic->getOwnerId(),
460           'name' => $topic->getOwner()->getDisplayName(),
461           'created' => $topic->getCreatedTime(),
462         ]];
463         $variables['topics'][$id]->submitted = drupal_render($forum_submitted);
464         $forum_submitted = [
465           '#theme' => 'forum_submitted',
466           '#topic' => isset($topic->last_reply) ? $topic->last_reply : NULL,
467         ];
468         $variables['topics'][$id]->last_reply = drupal_render($forum_submitted);
469
470         $variables['topics'][$id]->new_text = '';
471         $variables['topics'][$id]->new_url = '';
472
473         if ($topic->new_replies) {
474           $page_number = \Drupal::entityManager()->getStorage('comment')
475             ->getNewCommentPageNumber($topic->comment_count, $topic->new_replies, $topic, 'comment_forum');
476           $query = $page_number ? ['page' => $page_number] : NULL;
477           $variables['topics'][$id]->new_text = \Drupal::translation()->formatPlural($topic->new_replies, '1 new post<span class="visually-hidden"> in topic %title</span>', '@count new posts<span class="visually-hidden"> in topic %title</span>', ['%title' => $variables['topics'][$id]->label()]);
478           $variables['topics'][$id]->new_url = \Drupal::url('entity.node.canonical', ['node' => $topic->id()], ['query' => $query, 'fragment' => 'new']);
479         }
480
481         // Build table rows from topics.
482         $row = [];
483         $row[] = [
484           'data' => [
485             $topic->icon,
486             [
487               '#markup' => '<div class="forum__title"><div>' . $topic->title_link . '</div><div>' . $topic->submitted . '</div></div>',
488             ],
489           ],
490           'class' => ['forum__topic'],
491         ];
492
493         if ($topic->moved) {
494           $row[] = [
495             'data' => $topic->message,
496             'colspan' => '2',
497           ];
498         }
499         else {
500           $new_replies = '';
501           if ($topic->new_replies) {
502             $new_replies = '<br /><a href="' . $topic->new_url . '">' . $topic->new_text . '</a>';
503           }
504
505           $row[] = [
506             'data' => [
507               [
508                 '#prefix' => $topic->comment_count,
509                 '#markup' => $new_replies,
510               ],
511             ],
512             'class' => ['forum__replies'],
513           ];
514           $row[] = [
515             'data' => $topic->last_reply,
516             'class' => ['forum__last-reply'],
517           ];
518         }
519         $table['#rows'][] = $row;
520       }
521
522       $variables['topics'] = $table;
523       $variables['topics_pager'] = [
524         '#type' => 'pager',
525       ];
526     }
527   }
528 }
529
530 /**
531  * Prepares variables for forum list templates.
532  *
533  * Default template: forum-list.html.twig.
534  *
535  * @param array $variables
536  *   An array containing the following elements:
537  *   - forums: An array of all forum objects to display for the given taxonomy
538  *     term ID. If tid = 0 then all the top-level forums are displayed.
539  *   - parents: An array of taxonomy term objects that are ancestors of the
540  *     current term ID.
541  *   - tid: Taxonomy term ID of the current forum.
542  */
543 function template_preprocess_forum_list(&$variables) {
544   $user = \Drupal::currentUser();
545   $row = 0;
546   // Sanitize each forum so that the template can safely print the data.
547   foreach ($variables['forums'] as $id => $forum) {
548     $variables['forums'][$id]->description = ['#markup' => $forum->description->value];
549     $variables['forums'][$id]->link = forum_uri($forum);
550     $variables['forums'][$id]->name = $forum->label();
551     $variables['forums'][$id]->is_container = !empty($forum->forum_container->value);
552     $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
553     $row++;
554
555     $variables['forums'][$id]->new_text = '';
556     $variables['forums'][$id]->new_url = '';
557     $variables['forums'][$id]->new_topics = 0;
558     $variables['forums'][$id]->old_topics = $forum->num_topics;
559     $variables['forums'][$id]->icon_class = 'default';
560     $variables['forums'][$id]->icon_title = t('No new posts');
561     if ($user->isAuthenticated()) {
562       $variables['forums'][$id]->new_topics = \Drupal::service('forum_manager')->unreadTopics($forum->id(), $user->id());
563       if ($variables['forums'][$id]->new_topics) {
564         $variables['forums'][$id]->new_text = \Drupal::translation()->formatPlural($variables['forums'][$id]->new_topics, '1 new post<span class="visually-hidden"> in forum %title</span>', '@count new posts<span class="visually-hidden"> in forum %title</span>', ['%title' => $variables['forums'][$id]->label()]);
565         $variables['forums'][$id]->new_url = \Drupal::url('forum.page', ['taxonomy_term' => $forum->id()], ['fragment' => 'new']);
566         $variables['forums'][$id]->icon_class = 'new';
567         $variables['forums'][$id]->icon_title = t('New posts');
568       }
569       $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
570     }
571     $forum_submitted = ['#theme' => 'forum_submitted', '#topic' => $forum->last_post];
572     $variables['forums'][$id]->last_reply = drupal_render($forum_submitted);
573   }
574
575   $variables['pager'] = [
576    '#type' => 'pager',
577   ];
578
579   // Give meaning to $tid for themers. $tid actually stands for term ID.
580   $variables['forum_id'] = $variables['tid'];
581   unset($variables['tid']);
582 }
583
584 /**
585  * Prepares variables for forum icon templates.
586  *
587  * Default template: forum-icon.html.twig.
588  *
589  * @param array $variables
590  *   An array containing the following elements:
591  *   - new_posts: Indicates whether or not the topic contains new posts.
592  *   - num_posts: The total number of posts in all topics.
593  *   - comment_mode: An integer indicating whether comments are open, closed,
594  *     or hidden.
595  *   - sticky: Indicates whether the topic is sticky.
596  *   - first_new: Indicates whether this is the first topic with new posts.
597  */
598 function template_preprocess_forum_icon(&$variables) {
599   $variables['hot_threshold'] = \Drupal::config('forum.settings')->get('topics.hot_threshold');
600
601   if ($variables['num_posts'] > $variables['hot_threshold']) {
602     $variables['icon_status'] = $variables['new_posts'] ? 'hot-new' : 'hot';
603     $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
604   }
605   else {
606     $variables['icon_status'] = $variables['new_posts'] ? 'new' : 'default';
607     $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
608   }
609
610   if ($variables['comment_mode'] == CommentItemInterface::CLOSED || $variables['comment_mode'] == CommentItemInterface::HIDDEN) {
611     $variables['icon_status'] = 'closed';
612     $variables['icon_title'] = t('Closed topic');
613   }
614
615   if ($variables['sticky'] == 1) {
616     $variables['icon_status'] = 'sticky';
617     $variables['icon_title'] = t('Sticky topic');
618   }
619
620   $variables['attributes']['title'] = $variables['icon_title'];
621 }
622
623 /**
624  * Prepares variables for forum submission information templates.
625  *
626  * The submission information will be displayed in the forum list and topic
627  * list.
628  *
629  * Default template: forum-submitted.html.twig.
630  *
631  * @param array $variables
632  *   An array containing the following elements:
633  *   - topic: The topic object.
634  */
635 function template_preprocess_forum_submitted(&$variables) {
636   $variables['author'] = '';
637   if (isset($variables['topic']->uid)) {
638     $username = ['#theme' => 'username', '#account' => User::load($variables['topic']->uid)];
639     $variables['author'] = drupal_render($username);
640   }
641   $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatTimeDiffSince($variables['topic']->created) : '';
642 }