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