forum.module 29.3 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

Dries's avatar
 
Dries committed
3 4
/**
 * @file
5
 * Provides discussion forums.
Dries's avatar
 
Dries committed
6 7
 */

8
use Drupal\comment\CommentInterface;
9
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
10
use Drupal\Component\Utility\Xss;
11
use Drupal\Core\Entity\EntityInterface;
12
use Drupal\Component\Utility\String;
13
use Drupal\Core\Routing\RouteMatchInterface;
14

15
/**
16
 * Implements hook_help().
17
 */
18
function forum_help($route_name, RouteMatchInterface $route_match) {
19 20
  switch ($route_name) {
    case 'help.page.forum':
21 22 23 24 25 26 27 28 29 30 31 32
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Forum module lets you create threaded discussion forums with functionality similar to other message board systems. Forums are useful because they allow community members to discuss topics with one another while ensuring those conversations are archived for later reference. In a forum, users post topics and threads in nested hierarchies, allowing discussions to be categorized and grouped. The forum hierarchy consists of:') . '</p>';
      $output .= '<ul>';
      $output .= '<li>' . t('Optional containers (for example, <em>Support</em>), which can hold:') . '</li>';
      $output .= '<ul><li>' . t('Forums (for example, <em>Installing Drupal</em>), which can hold:') . '</li>';
      $output .= '<ul><li>' . t('Forum topics submitted by users (for example, <em>How to start a Drupal 6 Multisite</em>), which start discussions and are starting points for:') . '</li>';
      $output .= '<ul><li>' . t('Threaded comments submitted by users (for example, <em>You have these options...</em>).') . '</li>';
      $output .= '</ul>';
      $output .= '</ul>';
      $output .= '</ul>';
      $output .= '</ul>';
33
      $output .= '<p>' . t('For more information, see <a href="!forum">the online documentation for the Forum module</a>.', array('!forum' => 'https://drupal.org/documentation/modules/forum')) . '</p>';
34 35
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
36
      $output .= '<dt>' . t('Setting up forum structure') . '</dt>';
37
      $output .= '<dd>' . t('Visit the <a href="!forums">Forums page</a> to set up containers and forums to hold your discussion topics.', array('!forums' => \Drupal::url('forum.overview'))) . '</dd>';
38
      $output .= '<dt>' . t('Starting a discussion') . '</dt>';
39
      $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.', array('!create-topic' => \Drupal::url('node.add', array('node_type' => 'forum')), '!content-add' => \Drupal::url('node.add_page'))) . '</dd>';
40 41
      $output .= '<dt>' . t('Navigating in the Forum') . '</dt>';
      $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>.', array('!forums' => \Drupal::url('forum.index'))) . '</dd>';
42 43 44
      $output .= '<dt>' . t('Moving forum topics') . '</dt>';
      $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>';
      $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
45
      $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>';
46
      $output .= '</dl>';
47
      return $output;
48 49

    case 'forum.overview':
50
      $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
51 52
      $more_help_link = array(
        '#type' => 'link',
53 54
        '#route_name' => 'help.page',
        '#route_parameters' => array('name' => 'forum'),
55 56 57 58 59 60 61 62 63
        '#title' => t('More help'),
      );
      $container = array(
        '#theme' => 'container',
        '#children' => drupal_render($more_help_link),
        '#attributes' => array(
          'class' => array('more-help-link'),
        ),
      );
64
      $output .= drupal_render($container);
65
      return $output;
66 67

    case 'forum.add_container':
68
      return '<p>' . t('Use containers to group related forums.') . '</p>';
69 70

    case 'forum.add_forum':
71
      return '<p>' . t('A forum holds related forum topics.') . '</p>';
72 73

    case 'forum.settings':
74
      return '<p>' . t('Adjust the display of your forum topics. Organize the forums on the <a href="!forum-structure">forum structure page</a>.', array('!forum-structure' => \Drupal::url('forum.overview'))) . '</p>';
Dries's avatar
 
Dries committed
75 76 77
  }
}

78
/**
79
 * Implements hook_theme().
80 81 82
 */
function forum_theme() {
  return array(
83
    'forums' => array(
84
      'template' => 'forums',
85
      'variables' => array('forums' => array(), 'topics' => array(), 'topics_pager' => array(), 'parents' => NULL, 'term' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL, 'header' => array()),
86 87
    ),
    'forum_list' => array(
88
      'template' => 'forum-list',
89
      'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
90 91
    ),
    'forum_icon' => array(
92
      'template' => 'forum-icon',
93
      'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE),
94
    ),
95
    'forum_submitted' => array(
96
      'template' => 'forum-submitted',
97
      'variables' => array('topic' => NULL),
98
    ),
99
    'forum_form' => array(
100
      'render element' => 'form',
101
    ),
102 103 104
  );
}

105
/**
106
 * Implements hook_menu_local_tasks().
107
 */
108
function forum_menu_local_tasks(&$data, $route_name) {
109
  $user = \Drupal::currentUser();
110

111
  // Add action link to 'node/add/forum' on 'forum' sub-pages.
112
  if (in_array($route_name, array('forum.index', 'forum.page'))) {
113
    $forum_term = \Drupal::routeMatch()->getParameter('taxonomy_term');
114
    $vid = \Drupal::config('forum.settings')->get('vocabulary');
115 116
    $links = array();
    // Loop through all bundles for forum taxonomy vocabulary field.
117 118
    $field_map = \Drupal::entityManager()->getFieldMap();
    foreach ($field_map['node']['taxonomy_forums']['bundles'] as $type) {
119
      if (\Drupal::entityManager()->getAccessController('node')->createAccess($type)) {
120 121 122 123 124 125 126 127 128 129
        $links[$type] = array(
          '#theme' => 'menu_local_action',
          '#link' => array(
            'title' => t('Add new @node_type', array('@node_type' => entity_load('node_type', $type)->label())),
            'href' => 'node/add/' . $type,
          ),
        );
        if ($forum_term && $forum_term->bundle() == $vid) {
          // We are viewing a forum term (specific forum), append the tid to the
          // url.
130
          $links[$type]['#link']['localized_options']['query']['forum_id'] = $forum_term->id();
131 132
        }
      }
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    }
    if (empty($links)) {
      // Authenticated user does not have access to create new topics.
      if ($user->isAuthenticated()) {
        $links['disallowed'] = array(
          '#theme' => 'menu_local_action',
          '#link' => array(
            'title' => t('You are not allowed to post new content in the forum.'),
          ),
        );
      }
      // Anonymous user does not have access to create new topics.
      else {
        $links['login'] = array(
          '#theme' => 'menu_local_action',
          '#link' => array(
            'title' => t('<a href="@login">Log in</a> to post new content in the forum.', array(
              '@login' => url('user/login', array('query' => drupal_get_destination())),
            )),
            'localized_options' => array('html' => TRUE),
          ),
        );
155 156
      }
    }
157
    $data['actions'] += $links;
158 159
  }
}
160

161
/**
162
 * Implements hook_entity_type_build().
163
 */
164 165
function forum_entity_type_build(array &$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
166
  // Register forum specific forms.
167
  $entity_types['taxonomy_term']
168 169
    ->setFormClass('forum', 'Drupal\forum\Form\ForumForm')
    ->setFormClass('container', 'Drupal\forum\Form\ContainerForm')
170 171
    ->setLinkTemplate('forum-delete-form', 'forum.delete')
    ->setLinkTemplate('forum-edit-form', 'forum.edit_forum');
172 173
}

174
/**
175
 * Implements hook_entity_bundle_info_alter().
176
 */
177
function forum_entity_bundle_info_alter(&$bundles) {
178
  // Take over URI construction for taxonomy terms that are forums.
179
  if ($vid = \Drupal::config('forum.settings')->get('vocabulary')) {
180 181
    if (isset($bundles['taxonomy_term'][$vid])) {
      $bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
182 183 184 185 186
    }
  }
}

/**
187
 * Entity URI callback used in forum_entity_bundle_info_alter().
188 189 190
 */
function forum_uri($forum) {
  return array(
191 192 193 194
    'route_name' => 'forum.page',
    'route_parameters' => array(
      'taxonomy_term' => $forum->id(),
    ),
195 196 197
  );
}

198
/**
199
 * Implements hook_node_validate().
200
 *
201 202
 * Checks in particular that the node is assigned only a "leaf" term in the
 * forum taxonomy.
203
 */
204
function forum_node_validate(EntityInterface $node, $form, &$form_state) {
205
  if (\Drupal::service('forum_manager')->checkNodeType($node)) {
206
    // vocabulary is selected, not a "container" term.
207
    if (!$node->taxonomy_forums->isEmpty()) {
208
      // Extract the node's proper topic ID.
209
      foreach ($node->taxonomy_forums as $delta => $item) {
210 211
        // If no term was selected (e.g. when no terms exist yet), remove the
        // item.
212 213
        if (empty($item->target_id)) {
          unset($node->taxonomy_forums[$delta]);
214 215
          continue;
        }
216
        $term = $item->entity;
217
        if (!$term) {
218
          form_set_error('taxonomy_forums', $form_state, t('Select a forum.'));
219 220
          continue;
        }
221 222 223 224 225 226
        $used = \Drupal::entityQuery('taxonomy_term')
          ->condition('tid', $term->id())
          ->condition('vid', $term->bundle())
          ->range(0, 1)
          ->count()
          ->execute();
227
        if ($used && !empty($term->forum_container->value)) {
228
          form_set_error('taxonomy_forums', $form_state, t('The item %forum is a forum container, not a forum. Select one of the forums below instead.', array('%forum' => $term->getName())));
229 230
        }
      }
231 232 233
    }
  }
}
234

235
/**
236
 * Implements hook_ENTITY_TYPE_presave() for node entities.
237
 *
238
 * Assigns the forum taxonomy when adding a topic from within a forum.
239
 */
240
function forum_node_presave(EntityInterface $node) {
241
  if (\Drupal::service('forum_manager')->checkNodeType($node)) {
242 243
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
244 245
    if (!$node->taxonomy_forums->isEmpty()) {
      $node->forum_tid = $node->taxonomy_forums->target_id;
246 247
      // Only do a shadow copy check if this is not a new node.
      if (!$node->isNew()) {
248
        $old_tid = \Drupal::service('forum.index_storage')->getOriginalTermId($node);
249 250
        if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
          // A shadow copy needs to be created. Retain new term and add old term.
251
          $node->taxonomy_forums[count($node->taxonomy_forums)] = array('target_id' => $old_tid);
252
        }
253 254 255 256
      }
    }
  }
}
257

258
/**
259
 * Implements hook_ENTITY_TYPE_update() for node entities.
260
 */
261
function forum_node_update(EntityInterface $node) {
262
  if (\Drupal::service('forum_manager')->checkNodeType($node)) {
263 264
    // If this is not a new revision and does exist, update the forum record,
    // otherwise insert a new one.
265 266 267
    /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
    $forum_index_storage = \Drupal::service('forum.index_storage');
    if ($node->getRevisionId() == $node->original->getRevisionId() && $forum_index_storage->getOriginalTermId($node)) {
268
      if (!empty($node->forum_tid)) {
269
        $forum_index_storage->update($node);
270 271 272
      }
      // The node is removed from the forum.
      else {
273
        $forum_index_storage->delete($node);
274 275 276
      }
    }
    else {
277
      if (!empty($node->forum_tid)) {
278
        $forum_index_storage->create($node);
279
      }
280
    }
281 282
    // If the node has a shadow forum topic, update the record for this
    // revision.
283
    if (!empty($node->shadow)) {
284 285
      $forum_index_storage->deleteRevision($node);
      $forum_index_storage->create($node);
286 287 288 289
    }

    // If the node is published, update the forum index.
    if ($node->isPublished()) {
290 291
      $forum_index_storage->deleteIndex($node);
      $forum_index_storage->createIndex($node);
292 293 294
    }
    // When a forum node is unpublished, remove it from the forum_index table.
    else {
295
      $forum_index_storage->deleteIndex($node);
296
    }
297 298
  }
}
299

300
/**
301
 * Implements hook_ENTITY_TYPE_insert() for node entities.
302
 */
303
function forum_node_insert(EntityInterface $node) {
304
  if (\Drupal::service('forum_manager')->checkNodeType($node)) {
305 306
    /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
    $forum_index_storage = \Drupal::service('forum.index_storage');
307
    if (!empty($node->forum_tid)) {
308
      $forum_index_storage->create($node);
309
    }
310 311 312

    // If the node is published, update the forum index.
    if ($node->isPublished()) {
313
      $forum_index_storage->createIndex($node);
314
    }
315 316
  }
}
317

318
/**
319
 * Implements hook_ENTITY_TYPE_predelete() for node entities.
320
 */
321
function forum_node_predelete(EntityInterface $node) {
322
  if (\Drupal::service('forum_manager')->checkNodeType($node)) {
323 324 325 326
    /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
    $forum_index_storage = \Drupal::service('forum.index_storage');
    $forum_index_storage->delete($node);
    $forum_index_storage->deleteIndex($node);
327
  }
328
}
329

330
/**
331
 * Implements hook_ENTITY_TYPE_storage_load() for node entities.
332
 */
333
function forum_node_storage_load($nodes) {
334 335
  $node_vids = array();
  foreach ($nodes as $node) {
336
    if (\Drupal::service('forum_manager')->checkNodeType($node)) {
337
      $node_vids[] = $node->getRevisionId();
338 339 340
    }
  }
  if (!empty($node_vids)) {
341
    $result = \Drupal::service('forum.index_storage')->read($node_vids);
342 343 344
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
345
  }
346 347
}

348
/**
349
 * Implements hook_permission().
350
 */
351
function forum_permission() {
352
  $perms = array(
353 354 355
    'administer forums' => array(
      'title' => t('Administer forums'),
    ),
356 357
  );
  return $perms;
Dries's avatar
 
Dries committed
358
}
Dries's avatar
 
Dries committed
359

360
/**
361
 * Implements hook_ENTITY_TYPE_update() for comment entities.
362
 */
363
function forum_comment_update(CommentInterface $comment) {
364
  if ($comment->getCommentedEntityTypeId() == 'node') {
365
    \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
366 367 368 369
  }
}

/**
370
 * Implements hook_ENTITY_TYPE_insert() for comment entities.
371
 */
372
function forum_comment_insert(CommentInterface $comment) {
373
  if ($comment->getCommentedEntityTypeId() == 'node') {
374
    \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
375
  }
376 377 378
}

/**
379
 * Implements hook_ENTITY_TYPE_delete() for comment entities.
380
 */
381
function forum_comment_delete(CommentInterface $comment) {
382
  if ($comment->getCommentedEntityTypeId() == 'node') {
383
    \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
384
  }
385 386
}

387
/**
388
 * Implements hook_form_BASE_FORM_ID_alter().
389
 */
390
function forum_form_taxonomy_vocabulary_form_alter(&$form, &$form_state, $form_id) {
391
  $vid = \Drupal::config('forum.settings')->get('vocabulary');
392
  $vocabulary = $form_state['controller']->getEntity();
393
  if ($vid == $vocabulary->id()) {
394 395 396 397 398 399 400 401 402 403
    $form['help_forum_vocab'] = array(
      '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
      '#weight' => -1,
    );
    // Forum's vocabulary always has single hierarchy. Forums and containers
    // have only one parent or no parent for root items. By default this value
    // is 0.
    $form['hierarchy']['#value'] = TAXONOMY_HIERARCHY_SINGLE;
    // Do not allow to delete forum's vocabulary.
    $form['actions']['delete']['#access'] = FALSE;
404 405
    // Do not allow to change a vid of forum's vocabulary.
    $form['vid']['#disabled'] = TRUE;
406 407 408 409
  }
}

/**
410
 * Implements hook_form_FORM_ID_alter() for taxonomy_term_form().
411
 */
412
function forum_form_taxonomy_term_form_alter(&$form, &$form_state, $form_id) {
413
  $vid = \Drupal::config('forum.settings')->get('vocabulary');
414
  if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
415
    // Hide multiple parents select from forum terms.
416
    $form['relations']['parent']['#access'] = FALSE;
417
  }
418 419 420
}

/**
421
 * Implements hook_form_BASE_FORM_ID_alter() for node_form().
422 423
 */
function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
424 425 426 427 428 429 430 431 432 433 434 435 436 437
  $node = $form_state['controller']->getEntity();
  if (isset($node->taxonomy_forums) && !$node->isNew()) {
    $forum_terms = $node->taxonomy_forums;
    // If editing, give option to leave shadows.
    $shadow = (count($forum_terms) > 1);
    $form['shadow'] = array(
      '#type' => 'checkbox',
      '#title' => t('Leave shadow copy'),
      '#default_value' => $shadow,
      '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'),
    );
    $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
  }

438
  if (isset($form['taxonomy_forums'])) {
439
    $widget =& $form['taxonomy_forums']['widget'];
440
    // Make the vocabulary required for 'real' forum-nodes.
441 442 443
    $widget['#required'] = TRUE;
    $widget['#multiple'] = FALSE;
    if (empty($widget['#default_value'])) {
444
      // If there is no default forum already selected, try to get the forum
445 446
      // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
      // expect "2" to be the ID of the forum that was requested).
447
      $requested_forum_id = \Drupal::request()->query->get('forum_id');
448
      $widget['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
449
    }
450
  }
451 452
}

453
/**
454
 * Implements hook_preprocess_HOOK() for block templates.
455 456
 */
function forum_preprocess_block(&$variables) {
457
  if ($variables['configuration']['provider'] == 'forum') {
458
    $variables['attributes']['role'] = 'navigation';
459 460 461
  }
}

462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function forum_theme_suggestions_forums(array $variables) {
  $suggestions = array();
  $tid = $variables['term']->id();

  // Provide separate template suggestions based on what's being output. Topic
  // ID is also accounted for. Check both variables to be safe then the inverse.
  // Forums with topic IDs take precedence.
  if ($variables['forums'] && !$variables['topics']) {
    $suggestions[] = 'forums__containers';
    $suggestions[] = 'forums__' . $tid;
    $suggestions[] = 'forums__containers__' . $tid;
  }
  elseif (!$variables['forums'] && $variables['topics']) {
    $suggestions[] = 'forums__topics';
    $suggestions[] = 'forums__' . $tid;
    $suggestions[] = 'forums__topics__' . $tid;
  }
  else {
    $suggestions[] = 'forums__' . $tid;
  }

  return $suggestions;
}

Dries's avatar
 
Dries committed
489
/**
490
 * Prepares variables for forums templates.
491
 *
492 493 494
 * Default template: forums.html.twig.
 *
 * @param array $variables
495 496 497 498 499 500
 *   An array containing the following elements:
 *   - forums: An array of all forum objects to display for the given taxonomy
 *     term ID. If tid = 0 then all the top-level forums are displayed.
 *   - topics: An array of all the topics in the current forum.
 *   - parents: An array of taxonomy term objects that are ancestors of the
 *     current term ID.
501
 *   - term: Taxonomy term of the current forum.
502 503 504 505 506 507
 *   - sortby: One of the following integers indicating the sort criteria:
 *     - 1: Date - newest first.
 *     - 2: Date - oldest first.
 *     - 3: Posts with the most comments first.
 *     - 4: Posts with the least comments first.
 *   - forum_per_page: The maximum number of topics to display per page.
Dries's avatar
 
Dries committed
508
 */
509
function template_preprocess_forums(&$variables) {
510
  $variables['tid'] = $variables['term']->id();
511
  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
512
    if (!empty($variables['forums'])) {
513 514 515 516 517 518
      $variables['forums'] = array(
        '#theme' => 'forum_list',
        '#forums' => $variables['forums'],
        '#parents' => $variables['parents'],
        '#tid' => $variables['tid'],
      );
519
    }
Dries's avatar
 
Dries committed
520

521
    if ($variables['term'] && empty($variables['term']->forum_container->value) && !empty($variables['topics'])) {
522
      $forum_topic_list_header = $variables['header'];
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551

      $table = array(
        '#theme' => 'table__forum_topic_list',
        '#responsive' => FALSE,
        '#attributes' => array('id' => 'forum-topic-' . $variables['tid']),
        '#header' => array(),
        '#rows' => array(),
      );

      if (!empty($forum_topic_list_header)) {
        $table['#header'] = $forum_topic_list_header;
      }

      /** @var \Drupal\node\NodeInterface $topic */
      foreach ($variables['topics'] as $id => $topic) {
        $variables['topics'][$id]->icon = array(
          '#theme' => 'forum_icon',
          '#new_posts' => $topic->new,
          '#num_posts' => $topic->comment_count,
          '#comment_mode' => $topic->comment_mode,
          '#sticky' => $topic->isSticky(),
          '#first_new' => $topic->first_new,
        );

        // We keep the actual tid in forum table, if it's different from the
        // current tid then it means the topic appears in two forums, one of
        // them is a shadow copy.
        if ($variables['tid'] != $topic->forum_tid) {
          $variables['topics'][$id]->moved = TRUE;
552
          $variables['topics'][$id]->title = String::checkPlain($topic->getTitle());
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
          $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
        }
        else {
          $variables['topics'][$id]->moved = FALSE;
          $variables['topics'][$id]->title_link = l($topic->getTitle(), 'node/' . $topic->id());
          $variables['topics'][$id]->message = '';
        }
        $forum_submitted = array('#theme' => 'forum_submitted', '#topic' => (object) array(
          'uid' => $topic->getOwnerId(),
          'name' => $topic->getOwner()->getUsername(),
          'created' => $topic->getCreatedTime(),
        ));
        $variables['topics'][$id]->submitted = drupal_render($forum_submitted);
        $forum_submitted = array(
          '#theme' => 'forum_submitted',
          '#topic' => isset($topic->last_reply) ? $topic->last_reply : NULL,
        );
        $variables['topics'][$id]->last_reply = drupal_render($forum_submitted);

        $variables['topics'][$id]->new_text = '';
        $variables['topics'][$id]->new_url = '';

        if ($topic->new_replies) {
576 577 578
          $page_number = \Drupal::entityManager()->getStorage('comment')
            ->getNewCommentPageNumber($topic->comment_count, $topic->new_replies, $topic, 'comment_forum');
          $query = $page_number ? array('page' => $page_number) : NULL;
579
          $variables['topics'][$id]->new_text = format_plural($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>', array('%title' => $variables['topics'][$id]->label()));
580
          $variables['topics'][$id]->new_url = url('node/' . $topic->id(), array('query' => $query, 'fragment' => 'new'));
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
        }

        // Build table rows from topics.
        $row = array();
        $row[] = array(
          'data' => array(
            $topic->icon,
            array(
              '#markup' => '<div class="title"><div>' . $topic->title_link . '</div><div>' . $topic->submitted . '</div></div>',
            ),
          ),
          'class' => array('topic'),
        );

        if ($topic->moved) {
          $row[] = array(
            'data' => $topic->message,
            'colspan' => '2',
          );
        }
        else {
          $new_replies = '';
          if ($topic->new_replies) {
            $new_replies = '<br /><a href="' . $topic->new_url . '">' . $topic->new_text . '</a>';
          }

          $row[] = array(
            'data' => $topic->comment_count . $new_replies,
            'class' => array('replies'),
          );
          $row[] = array(
            'data' => $topic->last_reply,
            'class' => array('last-reply'),
          );
        }
        $table['#rows'][] = $row;
      }

      $variables['topics'] = $table;
      $variables['topics_pager'] = array(
        '#theme' => 'pager',
622
      );
Dries's avatar
 
Dries committed
623
    }
Dries's avatar
 
Dries committed
624 625 626
  }
}

Dries's avatar
 
Dries committed
627
/**
628
 * Prepares variables for forum list templates.
Dries's avatar
 
Dries committed
629
 *
630 631 632
 * Default template: forum-list.html.twig.
 *
 * @param array $variables
633 634 635 636 637 638
 *   An array containing the following elements:
 *   - forums: An array of all forum objects to display for the given taxonomy
 *     term ID. If tid = 0 then all the top-level forums are displayed.
 *   - parents: An array of taxonomy term objects that are ancestors of the
 *     current term ID.
 *   - tid: Taxonomy term ID of the current forum.
Dries's avatar
 
Dries committed
639
 */
640
function template_preprocess_forum_list(&$variables) {
641
  $user = \Drupal::currentUser();
642
  $row = 0;
643 644
  // Sanitize each forum so that the template can safely print the data.
  foreach ($variables['forums'] as $id => $forum) {
645
    $variables['forums'][$id]->description = Xss::filterAdmin($forum->description->value);
646
    $variables['forums'][$id]->link = url("forum/" . $forum->id());
647
    $variables['forums'][$id]->name = String::checkPlain($forum->label());
648
    $variables['forums'][$id]->is_container = !empty($forum->forum_container->value);
649 650 651 652 653 654 655
    $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
    $row++;

    $variables['forums'][$id]->new_text = '';
    $variables['forums'][$id]->new_url = '';
    $variables['forums'][$id]->new_topics = 0;
    $variables['forums'][$id]->old_topics = $forum->num_topics;
656 657
    $variables['forums'][$id]->icon_class = 'default';
    $variables['forums'][$id]->icon_title = t('No new posts');
658
    if ($user->isAuthenticated()) {
659
      $variables['forums'][$id]->new_topics = \Drupal::service('forum_manager')->unreadTopics($forum->id(), $user->id());
660
      if ($variables['forums'][$id]->new_topics) {
661
        $variables['forums'][$id]->new_text = format_plural($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>', array('%title' => $variables['forums'][$id]->label()));
662
        $variables['forums'][$id]->new_url = url('forum/' . $forum->id(), array('fragment' => 'new'));
663 664
        $variables['forums'][$id]->icon_class = 'new';
        $variables['forums'][$id]->icon_title = t('New posts');
Dries's avatar
 
Dries committed
665
      }
666
      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
Dries's avatar
 
Dries committed
667
    }
668 669
    $forum_submitted = array('#theme' => 'forum_submitted', '#topic' => $forum->last_post);
    $variables['forums'][$id]->last_reply = drupal_render($forum_submitted);
Dries's avatar
 
Dries committed
670
  }
671 672 673 674 675 676

  $variables['pager'] = array(
   '#theme' => 'pager',
  );

  // Give meaning to $tid for themers. $tid actually stands for term ID.
677 678
  $variables['forum_id'] = $variables['tid'];
  unset($variables['tid']);
Dries's avatar
 
Dries committed
679 680
}

681
/**
682
 * Prepares variables for forum icon templates.
683
 *
684 685 686
 * Default template: forum-icon.html.twig.
 *
 * @param array $variables
687 688 689 690 691 692 693
 *   An array containing the following elements:
 *   - new_posts: Indicates whether or not the topic contains new posts.
 *   - num_posts: The total number of posts in all topics.
 *   - comment_mode: An integer indicating whether comments are open, closed,
 *     or hidden.
 *   - sticky: Indicates whether the topic is sticky.
 *   - first_new: Indicates whether this is the first topic with new posts.
694
 */
695
function template_preprocess_forum_icon(&$variables) {
696
  $variables['hot_threshold'] = \Drupal::config('forum.settings')->get('topics.hot_threshold');
697

698
  if ($variables['num_posts'] > $variables['hot_threshold']) {
699
    $icon_status_class = $variables['new_posts'] ? 'hot-new' : 'hot';
700
    $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
Dries's avatar
 
Dries committed
701 702
  }
  else {
703
    $icon_status_class = $variables['new_posts'] ? 'new' : 'default';
704
    $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
Dries's avatar
 
Dries committed
705
  }
Dries's avatar
 
Dries committed
706

707
  if ($variables['comment_mode'] == CommentItemInterface::CLOSED || $variables['comment_mode'] == CommentItemInterface::HIDDEN) {
708
    $icon_status_class = 'closed';
709
    $variables['icon_title'] = t('Closed topic');
710 711
  }

712
  if ($variables['sticky'] == 1) {
713
    $icon_status_class = 'sticky';
714
    $variables['icon_title'] = t('Sticky topic');
715
  }
716 717 718 719

  $variables['attributes']['class'][] = 'icon';
  $variables['attributes']['class'][] = 'topic-status-' . $icon_status_class;
  $variables['attributes']['title'] = $variables['icon_title'];
720 721
}

722
/**
723
 * Prepares variables for forum submission information templates.
724 725 726
 *
 * The submission information will be displayed in the forum list and topic
 * list.
727
 *
728 729 730
 * Default template: forum-submitted.html.twig.
 *
 * @param array $variables
731 732
 *   An array containing the following elements:
 *   - topic: The topic object.
733
 */
734
function template_preprocess_forum_submitted(&$variables) {
735 736 737 738 739
  $variables['author'] = '';
  if (isset($variables['topic']->uid)) {
    $username = array('#theme' => 'username', '#account' => user_load($variables['topic']->uid));
    $variables['author'] = drupal_render($username);
  }
740
  $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date')->formatInterval(REQUEST_TIME - $variables['topic']->created) : '';
Dries's avatar
 
Dries committed
741 742
}

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
/**
 * Returns HTML for a forum form.
 *
 * By default this does not alter the appearance of a form at all, but is
 * provided as a convenience for themers.
 *
 * @param $variables
 *   An associative array containing:
 *   - form: A render element representing the form.
 *
 * @ingroup themeable
 */
function theme_forum_form(array $variables) {
  return drupal_render_children($variables['form']);
}