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 link 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 \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ 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