Same filename and directory in other branches
  1. 8.9.x core/modules/forum/forum.module
  2. 9 core/modules/forum/forum.module

Provides discussion forums.

File

core/modules/forum/forum.module
View source
<?php

/**
 * @file
 * Provides discussion forums.
 */
use Drupal\comment\CommentInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\language\Plugin\migrate\source\d7\LanguageContentSettingsTaxonomyVocabulary as D7LanguageContentSettingsTaxonomyVocabulary;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\taxonomy\Plugin\migrate\source\d6\Term as D6Term;
use Drupal\taxonomy\Plugin\migrate\source\d6\Vocabulary as D6Vocabulary;
use Drupal\taxonomy\Plugin\migrate\source\d6\VocabularyPerType as D6VocabularyPerType;
use Drupal\taxonomy\Plugin\migrate\source\d7\Term as D7Term;
use Drupal\taxonomy\Plugin\migrate\source\d7\TermEntityTranslation;
use Drupal\taxonomy\Plugin\migrate\source\d7\Vocabulary as D7Vocabulary;
use Drupal\taxonomy\Plugin\migrate\source\d7\VocabularyTranslation as D7VocabularyTranslation;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\user\Entity\User;

/**
 * Implements hook_help().
 */
function forum_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.forum':
      $output = '';
      $output .= '<h2>' . t('About') . '</h2>';
      $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>';
      $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>';
      $output .= '<p>' . t('A forum is represented by a hierarchical structure, consisting of:');
      $output .= '<ul>';
      $output .= '<li>' . t('<em>Forums</em> (for example, <em>Recipes for cooking vegetables</em>)') . '</li>';
      $output .= '<li>' . t('<em>Forum topics</em> submitted by users (for example, <em>How to cook potatoes</em>), which start discussions.') . '</li>';
      $output .= '<li>' . t('Threaded <em>comments</em> submitted by users (for example, <em>You wash the potatoes first and then...</em>).') . '</li>';
      $output .= '<li>' . t('Optional <em>containers</em>, used to group similar forums. Forums can be placed inside containers, and vice versa.') . '</li>';
      $output .= '</ul>';
      $output .= '</p>';
      $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>';
      $output .= '<h2>' . t('Uses') . '</h2>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Setting up the forum structure') . '</dt>';
      $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>';
      $output .= '<dt>' . t('Starting a discussion') . '</dt>';
      $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>';
      $output .= '<dt>' . t('Navigating in the forum') . '</dt>';
      $output .= '<dd>' . t('Installing 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>';
      $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>';
      $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>';
      $output .= '</dl>';
      return $output;
    case 'forum.overview':
      $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
      $more_help_link = [
        '#type' => 'link',
        '#url' => Url::fromRoute('help.page', [
          'name' => 'forum',
        ]),
        '#title' => t('More help'),
        '#attributes' => [
          'class' => [
            'icon-help',
          ],
        ],
      ];
      $container = [
        '#theme' => 'container',
        '#children' => $more_help_link,
        '#attributes' => [
          'class' => [
            'more-link',
          ],
        ],
      ];
      $output .= \Drupal::service('renderer')
        ->renderInIsolation($container);
      return $output;
    case 'forum.add_container':
      return '<p>' . t('Use containers to group related forums.') . '</p>';
    case 'forum.add_forum':
      return '<p>' . t('A forum holds related forum topics.') . '</p>';
    case 'forum.settings':
      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>';
  }
}

/**
 * Implements hook_theme().
 */
function forum_theme() {
  return [
    'forums' => [
      'variables' => [
        'forums' => [],
        'topics' => [],
        'topics_pager' => [],
        'parents' => NULL,
        'term' => NULL,
        'sortby' => NULL,
        'forum_per_page' => NULL,
        'header' => [],
      ],
    ],
    'forum_list' => [
      'variables' => [
        'forums' => NULL,
        'parents' => NULL,
        'tid' => NULL,
      ],
    ],
    'forum_icon' => [
      'variables' => [
        'new_posts' => NULL,
        'num_posts' => 0,
        'comment_mode' => 0,
        'sticky' => 0,
        'first_new' => FALSE,
      ],
    ],
    'forum_submitted' => [
      'variables' => [
        'topic' => NULL,
      ],
    ],
    'forum_topic' => [
      'variables' => [
        'title_link' => NULL,
        'submitted' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_entity_type_build().
 */
function forum_entity_type_build(array &$entity_types) {

  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */

  // Register forum specific forms.
  $entity_types['taxonomy_term']
    ->setFormClass('forum', 'Drupal\\forum\\Form\\ForumForm')
    ->setFormClass('container', 'Drupal\\forum\\Form\\ContainerForm')
    ->setLinkTemplate('forum-edit-container-form', '/admin/structure/forum/edit/container/{taxonomy_term}')
    ->setLinkTemplate('forum-delete-form', '/admin/structure/forum/delete/forum/{taxonomy_term}')
    ->setLinkTemplate('forum-edit-form', '/admin/structure/forum/edit/forum/{taxonomy_term}');
}

/**
 * Implements hook_entity_bundle_info_alter().
 */
function forum_entity_bundle_info_alter(&$bundles) {

  // Take over URI construction for taxonomy terms that are forums.
  if ($vid = \Drupal::config('forum.settings')
    ->get('vocabulary')) {
    if (isset($bundles['taxonomy_term'][$vid])) {
      $bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
    }
  }
}

/**
 * Entity URI callback used in forum_entity_bundle_info_alter().
 */
function forum_uri($forum) {
  return Url::fromRoute('forum.page', [
    'taxonomy_term' => $forum
      ->id(),
  ]);
}

/**
 * Implements hook_entity_bundle_field_info_alter().
 */
function forum_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type
    ->id() == 'node' && !empty($fields['taxonomy_forums'])) {
    $fields['taxonomy_forums']
      ->addConstraint('ForumLeaf', []);
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave() for node entities.
 *
 * Assigns the forum taxonomy when adding a topic from within a forum.
 */
function forum_node_presave(EntityInterface $node) {
  if (\Drupal::service('forum_manager')
    ->checkNodeType($node)) {

    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
    if (!$node->taxonomy_forums
      ->isEmpty()) {
      $node->forum_tid = $node->taxonomy_forums->target_id;

      // Only do a shadow copy check if this is not a new node.
      if (!$node
        ->isNew()) {
        $old_tid = \Drupal::service('forum.index_storage')
          ->getOriginalTermId($node);
        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.
          $node->taxonomy_forums[count($node->taxonomy_forums)] = [
            'target_id' => $old_tid,
          ];
        }
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for node entities.
 */
function forum_node_update(EntityInterface $node) {
  if (\Drupal::service('forum_manager')
    ->checkNodeType($node)) {

    // If this is not a new revision and does exist, update the forum record,
    // otherwise insert a new one.

    /** @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)) {
      if (!empty($node->forum_tid)) {
        $forum_index_storage
          ->update($node);
      }
      else {
        $forum_index_storage
          ->delete($node);
      }
    }
    else {
      if (!empty($node->forum_tid)) {
        $forum_index_storage
          ->create($node);
      }
    }

    // If the node has a shadow forum topic, update the record for this
    // revision.
    if (!empty($node->shadow)) {
      $forum_index_storage
        ->deleteRevision($node);
      $forum_index_storage
        ->create($node);
    }

    // If the node is published, update the forum index.
    if ($node
      ->isPublished()) {
      $forum_index_storage
        ->deleteIndex($node);
      $forum_index_storage
        ->createIndex($node);
    }
    else {
      $forum_index_storage
        ->deleteIndex($node);
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for node entities.
 */
function forum_node_insert(EntityInterface $node) {
  if (\Drupal::service('forum_manager')
    ->checkNodeType($node)) {

    /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
    $forum_index_storage = \Drupal::service('forum.index_storage');
    if (!empty($node->forum_tid)) {
      $forum_index_storage
        ->create($node);
    }

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

/**
 * Implements hook_ENTITY_TYPE_predelete() for node entities.
 */
function forum_node_predelete(EntityInterface $node) {
  if (\Drupal::service('forum_manager')
    ->checkNodeType($node)) {

    /** @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);
  }
}

/**
 * Implements hook_ENTITY_TYPE_storage_load() for node entities.
 */
function forum_node_storage_load($nodes) {
  $node_vids = [];
  foreach ($nodes as $node) {
    if (\Drupal::service('forum_manager')
      ->checkNodeType($node)) {
      $node_vids[] = $node
        ->getRevisionId();
    }
  }
  if (!empty($node_vids)) {
    $result = \Drupal::service('forum.index_storage')
      ->read($node_vids);
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for comment entities.
 */
function forum_comment_update(CommentInterface $comment) {
  if ($comment
    ->getCommentedEntityTypeId() == 'node') {
    \Drupal::service('forum.index_storage')
      ->updateIndex($comment
      ->getCommentedEntity());
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for comment entities.
 */
function forum_comment_insert(CommentInterface $comment) {
  if ($comment
    ->getCommentedEntityTypeId() == 'node') {
    \Drupal::service('forum.index_storage')
      ->updateIndex($comment
      ->getCommentedEntity());
  }
}

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

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\taxonomy\VocabularyForm.
 */
function forum_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $vid = \Drupal::config('forum.settings')
    ->get('vocabulary');
  $vocabulary = $form_state
    ->getFormObject()
    ->getEntity();
  if ($vid == $vocabulary
    ->id()) {
    $form['help_forum_vocab'] = [
      '#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'] = VocabularyInterface::HIERARCHY_SINGLE;

    // Do not allow to delete forum's vocabulary.
    $form['actions']['delete']['#access'] = FALSE;

    // Do not allow to change a vid of forum's vocabulary.
    $form['vid']['#disabled'] = TRUE;
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for \Drupal\taxonomy\TermForm.
 */
function forum_form_taxonomy_term_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $vid = \Drupal::config('forum.settings')
    ->get('vocabulary');
  if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {

    // Hide multiple parents select from forum terms.
    $form['relations']['parent']['#access'] = FALSE;
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
 */
function forum_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $node = $form_state
    ->getFormObject()
    ->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'] = [
      '#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'] = [
      '#type' => 'value',
      '#value' => $node->forum_tid,
    ];
  }
  if (isset($form['taxonomy_forums'])) {
    $widget =& $form['taxonomy_forums']['widget'];
    $widget['#multiple'] = FALSE;
    if (empty($widget['#default_value'])) {

      // If there is no default forum already selected, try to get the forum
      // 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).
      $requested_forum_id = \Drupal::request()->query
        ->get('forum_id');
      $widget['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
    }
  }
}

/**
 * Implements hook_preprocess_HOOK() for block templates.
 */
function forum_preprocess_block(&$variables) {
  if ($variables['configuration']['provider'] == 'forum') {
    $variables['attributes']['role'] = 'navigation';
  }
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function forum_theme_suggestions_forums(array $variables) {
  $suggestions = [];
  $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;
}

/**
 * Prepares variables for forums templates.
 *
 * Default template: forums.html.twig.
 *
 * @param array $variables
 *   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.
 *   - term: Taxonomy term of the current forum.
 *   - 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.
 */
function template_preprocess_forums(&$variables) {
  $variables['tid'] = $variables['term']
    ->id();
  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
    if (!empty($variables['forums'])) {
      $variables['forums'] = [
        '#theme' => 'forum_list',
        '#forums' => $variables['forums'],
        '#parents' => $variables['parents'],
        '#tid' => $variables['tid'],
      ];
    }
    if ($variables['term'] && empty($variables['term']->forum_container->value) && !empty($variables['topics'])) {
      $forum_topic_list_header = $variables['header'];
      $table = [
        '#theme' => 'table__forum_topic_list',
        '#responsive' => FALSE,
        '#attributes' => [
          'id' => 'forum-topic-' . $variables['tid'],
        ],
        '#header' => [],
        '#rows' => [],
      ];
      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 = [
          '#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;
          $variables['topics'][$id]->title = $topic
            ->getTitle();
          $variables['topics'][$id]->message = Link::fromTextAndUrl(t('This topic has been moved'), Url::fromRoute('forum.page', [
            'taxonomy_term' => $topic->forum_tid,
          ]))
            ->toString();
        }
        else {
          $variables['topics'][$id]->moved = FALSE;
          $variables['topics'][$id]->title_link = Link::fromTextAndUrl($topic
            ->getTitle(), $topic
            ->toUrl())
            ->toString();
          $variables['topics'][$id]->message = '';
        }
        $forum_submitted = [
          '#theme' => 'forum_submitted',
          '#topic' => (object) [
            'uid' => $topic
              ->getOwnerId(),
            'name' => $topic
              ->getOwner()
              ->getDisplayName(),
            'created' => $topic
              ->getCreatedTime(),
          ],
        ];
        $variables['topics'][$id]->submitted = \Drupal::service('renderer')
          ->render($forum_submitted);
        $forum_submitted = [
          '#theme' => 'forum_submitted',
          '#topic' => $topic->last_reply ?? NULL,
        ];
        $variables['topics'][$id]->last_reply = \Drupal::service('renderer')
          ->render($forum_submitted);
        $variables['topics'][$id]->new_text = '';
        $variables['topics'][$id]->new_url = '';
        if ($topic->new_replies) {
          $page_number = \Drupal::entityTypeManager()
            ->getStorage('comment')
            ->getNewCommentPageNumber($topic->comment_count, $topic->new_replies, $topic, 'comment_forum');
          $query = $page_number ? [
            'page' => $page_number,
          ] : NULL;
          $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(),
          ]);
          $variables['topics'][$id]->new_url = Url::fromRoute('entity.node.canonical', [
            'node' => $topic
              ->id(),
          ], [
            'query' => $query,
            'fragment' => 'new',
          ])
            ->toString();
        }

        // Build table rows from topics.
        $row = [];
        $row[] = [
          'data' => [
            $topic->icon,
            [
              '#theme' => 'forum_topic',
              '#title_link' => $topic->title_link,
              '#submitted' => $topic->submitted,
            ],
          ],
          'class' => [
            'forum__topic',
          ],
        ];
        if ($topic->moved) {
          $row[] = [
            '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[] = [
            'data' => [
              [
                '#prefix' => $topic->comment_count,
                '#markup' => $new_replies,
              ],
            ],
            'class' => [
              'forum__replies',
            ],
          ];
          $row[] = [
            'data' => $topic->last_reply,
            'class' => [
              'forum__last-reply',
            ],
          ];
        }
        $table['#rows'][] = $row;
      }
      $variables['topics_original'] = $variables['topics'];
      $variables['topics'] = $table;
      $variables['topics_pager'] = [
        '#type' => 'pager',
      ];
    }
  }
}

/**
 * Prepares variables for forum list templates.
 *
 * Default template: forum-list.html.twig.
 *
 * @param array $variables
 *   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.
 */
function template_preprocess_forum_list(&$variables) {
  $user = \Drupal::currentUser();
  $row = 0;

  // Sanitize each forum so that the template can safely print the data.
  foreach ($variables['forums'] as $id => $forum) {
    $variables['forums'][$id]->description = [
      '#markup' => $forum->description->value,
    ];
    $variables['forums'][$id]->link = forum_uri($forum);
    $variables['forums'][$id]->name = $forum
      ->label();
    $variables['forums'][$id]->is_container = !empty($forum->forum_container->value);
    $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;
    $variables['forums'][$id]->icon_class = 'default';
    $variables['forums'][$id]->icon_title = t('No new posts');
    if ($user
      ->isAuthenticated()) {
      $variables['forums'][$id]->new_topics = \Drupal::service('forum_manager')
        ->unreadTopics($forum
        ->id(), $user
        ->id());
      if ($variables['forums'][$id]->new_topics) {
        $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(),
        ]);
        $variables['forums'][$id]->new_url = Url::fromRoute('forum.page', [
          'taxonomy_term' => $forum
            ->id(),
        ], [
          'fragment' => 'new',
        ])
          ->toString();
        $variables['forums'][$id]->icon_class = 'new';
        $variables['forums'][$id]->icon_title = t('New posts');
      }
      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
    }
    $forum_submitted = [
      '#theme' => 'forum_submitted',
      '#topic' => $forum->last_post,
    ];
    $variables['forums'][$id]->last_reply = \Drupal::service('renderer')
      ->render($forum_submitted);
  }
  $variables['pager'] = [
    '#type' => 'pager',
  ];

  // Give meaning to $tid for themers. $tid actually stands for term ID.
  $variables['forum_id'] = $variables['tid'];
  unset($variables['tid']);
}

/**
 * Prepares variables for forum icon templates.
 *
 * Default template: forum-icon.html.twig.
 *
 * @param array $variables
 *   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.
 */
function template_preprocess_forum_icon(&$variables) {
  $variables['hot_threshold'] = \Drupal::config('forum.settings')
    ->get('topics.hot_threshold');
  if ($variables['num_posts'] > $variables['hot_threshold']) {
    $variables['icon_status'] = $variables['new_posts'] ? 'hot-new' : 'hot';
    $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
  }
  else {
    $variables['icon_status'] = $variables['new_posts'] ? 'new' : 'default';
    $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
  }
  if ($variables['comment_mode'] == CommentItemInterface::CLOSED || $variables['comment_mode'] == CommentItemInterface::HIDDEN) {
    $variables['icon_status'] = 'closed';
    $variables['icon_title'] = t('Closed topic');
  }
  if ($variables['sticky'] == 1) {
    $variables['icon_status'] = 'sticky';
    $variables['icon_title'] = t('Sticky topic');
  }
  $variables['attributes']['title'] = $variables['icon_title'];
}

/**
 * Prepares variables for forum submission information templates.
 *
 * The submission information will be displayed in the forum list and topic
 * list.
 *
 * Default template: forum-submitted.html.twig.
 *
 * @param array $variables
 *   An array containing the following elements:
 *   - topic: The topic object.
 */
function template_preprocess_forum_submitted(&$variables) {
  $variables['author'] = '';
  if (isset($variables['topic']->uid)) {
    $username = [
      '#theme' => 'username',
      '#account' => User::load($variables['topic']->uid),
    ];
    $variables['author'] = \Drupal::service('renderer')
      ->render($username);
  }
  $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')
    ->formatTimeDiffSince($variables['topic']->created) : '';
}

/**
 * Implements hook_migrate_prepare_row().
 */
function forum_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
  $source_plugin = $migration
    ->getSourcePlugin();
  if (is_a($source_plugin, D6Term::class) || is_a($source_plugin, D7Term::class) || is_a($source_plugin, TermEntityTranslation::class)) {
    $connection = $source_plugin
      ->getDatabase();
    if ($connection) {
      if ($connection
        ->schema()
        ->tableExists('variable')) {
        $query = $connection
          ->select('variable', 'v')
          ->fields('v', [
          'value',
        ])
          ->condition('name', 'forum_containers');
        $result = $query
          ->execute()
          ->fetchCol();
        if ($result) {
          $forum_container_tids = unserialize($result[0], [
            'allowed_classes' => FALSE,
          ]);
          $current_tid = $row
            ->getSourceProperty('tid');
          $row
            ->setSourceProperty('is_container', in_array($current_tid, $forum_container_tids));
        }
      }
    }
  }
}

/**
 * Implements hook_migrate_MIGRATION_ID_prepare_row().
 */
function forum_migrate_d7_taxonomy_vocabulary_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {

  // If the vocabulary being migrated is the one defined in the
  // 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
  // property to true so we know this is the vocabulary used by Forum.
  $connection = $migration
    ->getSourcePlugin()
    ->getDatabase();
  if ($connection) {
    if ($connection
      ->schema()
      ->tableExists('variable')) {
      $query = $connection
        ->select('variable', 'v')
        ->fields('v', [
        'value',
      ])
        ->condition('name', 'forum_nav_vocabulary');
      $result = $query
        ->execute()
        ->fetchCol();
      if ($result) {
        $forum_nav_vocabulary = unserialize($result[0], [
          'allowed_classes' => FALSE,
        ]);
        if ($forum_nav_vocabulary == $row
          ->getSourceProperty('vid')) {
          $row
            ->setSourceProperty('forum_vocabulary', TRUE);
        }
      }
    }
  }
}

/**
 * Implements hook_migration_plugins_alter().
 */
function forum_migration_plugins_alter(array &$migrations) {

  // Function to append the forum_vocabulary process plugin.
  $merge_forum_vocabulary = function ($process) {
    $process[] = [
      'plugin' => 'forum_vocabulary',
      'machine_name' => 'forums',
    ];
    return $process;
  };
  $merge_forum_field_name = function ($process) {
    $process[] = [
      'plugin' => 'forum_vocabulary',
      'machine_name' => 'taxonomy_forums',
    ];
    return $process;
  };
  foreach ($migrations as $migration_id => $migration) {

    // Add process for forum_nav_vocabulary.

    /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */
    $source_plugin = \Drupal::service('plugin.manager.migration')
      ->createStubMigration($migration)
      ->getSourcePlugin();
    if (is_a($source_plugin, D6Vocabulary::class) || is_a($source_plugin, D6VocabularyPerType::class)) {
      if (isset($migration['process']['vid'])) {
        $migrations[$migration_id]['process']['vid'] = $merge_forum_vocabulary($migration['process']['vid']);
      }
      if (isset($migration['process']['field_name'])) {
        $migrations[$migration_id]['process']['field_name'] = $merge_forum_field_name($migration['process']['field_name']);
      }
    }
    if (is_a($source_plugin, D7Vocabulary::class) && !is_a($source_plugin, D7VocabularyTranslation::class) && !is_a($source_plugin, D7LanguageContentSettingsTaxonomyVocabulary::class)) {
      if (isset($migration['process']['vid'])) {
        $process[] = $migrations[$migration_id]['process']['vid'];
        $migrations[$migration_id]['process']['vid'] = $merge_forum_vocabulary($process);
      }
    }

    // Add process for forum_container.
    if (is_a($source_plugin, D6Term::class) || is_a($source_plugin, D7Term::class) || is_a($source_plugin, TermEntityTranslation::class)) {
      $migrations[$migration_id]['process']['forum_container'] = 'is_container';
    }
  }
}

Functions

Namesort descending Description
forum_comment_delete Implements hook_ENTITY_TYPE_delete() for comment entities.
forum_comment_insert Implements hook_ENTITY_TYPE_insert() for comment entities.
forum_comment_update Implements hook_ENTITY_TYPE_update() for comment entities.
forum_entity_bundle_field_info_alter Implements hook_entity_bundle_field_info_alter().
forum_entity_bundle_info_alter Implements hook_entity_bundle_info_alter().
forum_entity_type_build Implements hook_entity_type_build().
forum_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
forum_form_taxonomy_term_form_alter Implements hook_form_FORM_ID_alter() for \Drupal\taxonomy\TermForm.
forum_form_taxonomy_vocabulary_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\taxonomy\VocabularyForm.
forum_help Implements hook_help().
forum_migrate_d7_taxonomy_vocabulary_prepare_row Implements hook_migrate_MIGRATION_ID_prepare_row().
forum_migrate_prepare_row Implements hook_migrate_prepare_row().
forum_migration_plugins_alter Implements hook_migration_plugins_alter().
forum_node_insert Implements hook_ENTITY_TYPE_insert() for node entities.
forum_node_predelete Implements hook_ENTITY_TYPE_predelete() for node entities.
forum_node_presave Implements hook_ENTITY_TYPE_presave() for node entities.
forum_node_storage_load Implements hook_ENTITY_TYPE_storage_load() for node entities.
forum_node_update Implements hook_ENTITY_TYPE_update() for node entities.
forum_preprocess_block Implements hook_preprocess_HOOK() for block templates.
forum_theme Implements hook_theme().
forum_theme_suggestions_forums Implements hook_theme_suggestions_HOOK().
forum_uri Entity URI callback used in forum_entity_bundle_info_alter().
template_preprocess_forums Prepares variables for forums templates.
template_preprocess_forum_icon Prepares variables for forum icon templates.
template_preprocess_forum_list Prepares variables for forum list templates.
template_preprocess_forum_submitted Prepares variables for forum submission information templates.