diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index c5af0f4..bb71da9 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -93,18 +93,11 @@ function forum_theme() {
function forum_menu() {
$items['forum'] = array(
'title' => 'Forums',
- 'page callback' => 'forum_page',
- 'access arguments' => array('access content'),
- 'file' => 'forum.pages.inc',
+ 'route_name' => 'forum_page',
);
- $items['forum/%forum_forum'] = array(
+ $items['forum/%forum'] = array(
'title' => 'Forums',
- 'title callback' => 'entity_page_label',
- 'title arguments' => array(1),
- 'page callback' => 'forum_page',
- 'page arguments' => array(1),
- 'access arguments' => array('access content'),
- 'file' => 'forum.pages.inc',
+ 'route_name' => 'forum_page',
);
$items['admin/structure/forum'] = array(
'title' => 'Forums',
@@ -168,48 +161,52 @@ function forum_menu_local_tasks(&$data, $router_item, $root_path) {
// Add action link to 'node/add/forum' on 'forum' sub-pages.
if ($root_path == 'forum' || $root_path == 'forum/%') {
- $tid = (isset($router_item['page_arguments'][0]) ? $router_item['page_arguments'][0]->id() : 0);
- $forum_term = forum_forum_load($tid);
- if ($forum_term) {
- $links = array();
- // Loop through all bundles for forum taxonomy vocabulary field.
- $field = field_info_field('taxonomy_forums');
- foreach ($field['bundles']['node'] as $type) {
- if (node_access('create', $type)) {
- $links[$type] = array(
- '#theme' => 'menu_local_action',
- '#link' => array(
- 'title' => t('Add new @node_type', array('@node_type' => node_type_get_label($type))),
- 'href' => 'node/add/' . $type . '/' . $forum_term->id(),
- ),
- );
+ $request = Drupal::request();
+ $forum_term = $request->attributes->get('taxonomy_term');
+ $vid = Drupal::config('forum.settings')->get('vocabulary');
+ $links = array();
+ // Loop through all bundles for forum taxonomy vocabulary field.
+ $field = Field::fieldInfo()->getField('taxonomy_forums');
+ foreach ($field['bundles']['node'] as $type) {
+ if (node_access('create', $type)) {
+ $links[$type] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'title' => t('Add new @node_type', array('@node_type' => node_type_get_label($type))),
+ '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.
+ $links[$type]['#link']['href'] .= '/' . $forum_term->id();
}
}
- if (empty($links)) {
- // Authenticated user does not have access to create new topics.
- if ($user->uid) {
- $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('Log in to post new content in the forum.', array(
- '@login' => url('user/login', array('query' => drupal_get_destination())),
- )),
- 'localized_options' => array('html' => TRUE),
- ),
- );
- }
+ }
+ if (empty($links)) {
+ // Authenticated user does not have access to create new topics.
+ if ($user->uid) {
+ $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('Log in to post new content in the forum.', array(
+ '@login' => url('user/login', array('query' => drupal_get_destination())),
+ )),
+ 'localized_options' => array('html' => TRUE),
+ ),
+ );
}
- $data['actions'] += $links;
}
+ $data['actions'] += $links;
}
}
@@ -238,7 +235,7 @@ function forum_entity_bundle_info_alter(&$bundles) {
}
/**
- * Entity URI callback used in forum_entity_info_alter().
+ * Entity URI callback used in forum_entity_bundle_info_alter().
*/
function forum_uri($forum) {
return array(
@@ -265,7 +262,7 @@ function _forum_node_check_node_type(EntityInterface $node) {
* Implements hook_node_view().
*/
function forum_node_view(EntityInterface $node, EntityDisplay $display, $view_mode) {
- $vid = config('forum.settings')->get('vocabulary');
+ $vid = Drupal::config('forum.settings')->get('vocabulary');
$vocabulary = taxonomy_vocabulary_load($vid);
if (_forum_node_check_node_type($node)) {
if ($view_mode == 'full' && node_is_page($node)) {
@@ -285,6 +282,29 @@ function forum_node_view(EntityInterface $node, EntityDisplay $display, $view_mo
}
/**
+ * Implements hook_taxonomy_term_load().
+ *
+ * @param array $entities
+ * An array of taxonomy term entities, indexed by tid.
+ */
+function forum_taxonomy_term_load(array $entities) {
+ $config = config('forum.settings');
+ $vid = $config->get('vocabulary');
+ foreach ($entities as $tid => &$forum_term) {
+ if ($forum_term->bundle() != $vid) {
+ // Not a forum term.
+ continue;
+ }
+
+ // Determine if the requested term is a container.
+ if (in_array($forum_term->id(), $config->get('containers'))) {
+ $forum_term->container = TRUE;
+ }
+ }
+ return $entities;
+}
+
+/**
* Implements hook_node_validate().
*
* Checks in particular that the node is assigned only a "leaf" term in the
@@ -688,132 +708,6 @@ function forum_form(EntityInterface $node, &$form_state) {
}
/**
- * Returns a tree of all forums for a given taxonomy term ID.
- *
- * @param $tid
- * (optional) Taxonomy term ID of the forum. If not given all forums will be
- * returned.
- *
- * @return
- * A tree of taxonomy objects, with the following additional properties:
- * - num_topics: Number of topics in the forum.
- * - num_posts: Total number of posts in all topics.
- * - last_post: Most recent post for the forum.
- * - forums: An array of child forums.
- */
-function forum_forum_load($tid = NULL) {
- $cache = &drupal_static(__FUNCTION__, array());
-
- // Return a cached forum tree if available.
- if (!isset($tid)) {
- $tid = 0;
- }
- if (isset($cache[$tid])) {
- return $cache[$tid];
- }
-
- $config = config('forum.settings');
- $vid = $config->get('vocabulary');
-
- // Load and validate the parent term.
- if ($tid) {
- $forum_term = taxonomy_term_load($tid);
- if (!$forum_term || ($forum_term->bundle() != $vid)) {
- return $cache[$tid] = FALSE;
- }
- }
- // If $tid is 0, create an empty entity to hold the child terms.
- elseif ($tid === 0) {
- $forum_term = entity_create('taxonomy_term', array(
- 'tid' => 0,
- 'vid' => $vid,
- ));
- }
-
- // Determine if the requested term is a container.
- if (!$forum_term->id() || in_array($forum_term->id(), $config->get('containers'))) {
- $forum_term->container = 1;
- }
-
- // Load parent terms.
- $forum_term->parents = taxonomy_term_load_parents_all($forum_term->id());
-
- // Load the tree below.
- $forums = array();
- $_forums = taxonomy_get_tree($vid, $tid, NULL, TRUE);
-
- if (count($_forums)) {
- $query = db_select('node_field_data', 'n');
- $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
- $query->join('forum', 'f', 'n.vid = f.vid');
- $query->addExpression('COUNT(n.nid)', 'topic_count');
- $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
- $counts = $query
- ->fields('f', array('tid'))
- ->condition('n.status', 1)
- // @todo This should be actually filtering on the desired node status
- // field language and just fall back to the default language.
- ->condition('n.default_langcode', 1)
- ->groupBy('tid')
- ->addTag('node_access')
- ->execute()
- ->fetchAllAssoc('tid');
- }
-
- foreach ($_forums as $forum) {
- // Determine if the child term is a container.
- if (in_array($forum->id(), $config->get('containers'))) {
- $forum->container = 1;
- }
-
- // Merge in the topic and post counters.
- if (!empty($counts[$forum->id()])) {
- $forum->num_topics = $counts[$forum->id()]->topic_count;
- $forum->num_posts = $counts[$forum->id()]->topic_count + $counts[$forum->id()]->comment_count;
- }
- else {
- $forum->num_topics = 0;
- $forum->num_posts = 0;
- }
-
- // Query "Last Post" information for this forum.
- $query = db_select('node_field_data', 'n');
- $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->id()));
- $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
- $query->join('users', 'u', 'ncs.last_comment_uid = u.uid');
- $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u.name END', 'last_comment_name');
-
- $topic = $query
- ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
- ->condition('n.status', 1)
- // @todo This should be actually filtering on the desired node status
- // field language and just fall back to the default language.
- ->condition('n.default_langcode', 1)
- ->orderBy('last_comment_timestamp', 'DESC')
- ->range(0, 1)
- ->addTag('node_access')
- ->execute()
- ->fetchObject();
-
- // Merge in the "Last Post" information.
- $last_post = new stdClass();
- if (!empty($topic->last_comment_timestamp)) {
- $last_post->created = $topic->last_comment_timestamp;
- $last_post->name = $topic->last_comment_name;
- $last_post->uid = $topic->last_comment_uid;
- }
- $forum->last_post = $last_post;
-
- $forums[$forum->id()] = $forum;
- }
-
- // Cache the result, and return the tree.
- $forum_term->forums = $forums;
- $cache[$tid] = $forum_term;
- return $forum_term;
-}
-
-/**
* Calculates the number of new posts in a forum that the user has not yet read.
*
* Nodes are new if they are newer than HISTORY_READ_LIMIT.
@@ -844,147 +738,6 @@ function _forum_topics_unread($term, $uid) {
}
/**
- * Gets all the topics in a forum.
- *
- * @param $tid
- * The term ID of the forum.
- * @param $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.
- * @param $forum_per_page
- * The maximum number of topics to display per page.
- *
- * @return
- * A list of all the topics in a forum.
- */
-function forum_get_topics($tid, $sortby, $forum_per_page) {
- global $user, $forum_topic_list_header;
-
- $forum_topic_list_header = array(
- array('data' => t('Topic'), 'field' => 'f.title'),
- array('data' => t('Replies'), 'field' => 'f.comment_count'),
- array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
- );
-
- $order = _forum_get_topic_order($sortby);
- for ($i = 0; $i < count($forum_topic_list_header); $i++) {
- if ($forum_topic_list_header[$i]['field'] == $order['field']) {
- $forum_topic_list_header[$i]['sort'] = $order['sort'];
- }
- }
-
- $query = db_select('forum_index', 'f')
- ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
- ->extend('Drupal\Core\Database\Query\TableSortExtender');
- $query->fields('f');
- $query
- ->condition('f.tid', $tid)
- ->addTag('node_access')
- ->addMetaData('base_table', 'forum_index')
- ->orderBy('f.sticky', 'DESC')
- ->orderByHeader($forum_topic_list_header)
- ->limit($forum_per_page);
-
- $count_query = db_select('forum_index', 'f');
- $count_query->condition('f.tid', $tid);
- $count_query->addExpression('COUNT(*)');
- $count_query->addTag('node_access');
- $count_query->addMetaData('base_table', 'forum_index');
-
- $query->setCountQuery($count_query);
- $result = $query->execute();
- $nids = array();
- foreach ($result as $record) {
- $nids[] = $record->nid;
- }
- if ($nids) {
- $nodes = node_load_multiple($nids);
-
- $query = db_select('node_field_data', 'n')
- ->extend('Drupal\Core\Database\Query\TableSortExtender');
- $query->fields('n', array('nid'));
-
- $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
- $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count'));
-
- $query->join('forum_index', 'f', 'f.nid = ncs.nid');
- $query->addField('f', 'tid', 'forum_tid');
-
- $query->join('users', 'u', 'n.uid = u.uid');
- $query->addField('u', 'name');
-
- $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
-
- $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
-
- $query
- ->orderBy('f.sticky', 'DESC')
- ->orderByHeader($forum_topic_list_header)
- ->condition('n.nid', $nids)
- // @todo This should be actually filtering on the desired node language
- // and just fall back to the default language.
- ->condition('n.default_langcode', 1);
-
- $result = array();
- foreach ($query->execute() as $row) {
- $topic = $nodes[$row->nid];
- $topic->comment_mode = $topic->comment;
-
- foreach ($row as $key => $value) {
- $topic->{$key} = $value;
- }
- $result[] = $topic;
- }
- }
- else {
- $result = array();
- }
-
- $topics = array();
- $first_new_found = FALSE;
- foreach ($result as $topic) {
- if ($user->uid) {
- // A forum is new if the topic is new, or if there are new comments since
- // the user's last visit.
- if ($topic->forum_tid != $tid) {
- $topic->new = 0;
- }
- else {
- $history = _forum_user_last_visit($topic->nid);
- $topic->new_replies = comment_num_new($topic->nid, $history);
- $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
- }
- }
- else {
- // Do not track "new replies" status for topics if the user is anonymous.
- $topic->new_replies = 0;
- $topic->new = 0;
- }
-
- // Make sure only one topic is indicated as the first new topic.
- $topic->first_new = FALSE;
- if ($topic->new != 0 && !$first_new_found) {
- $topic->first_new = TRUE;
- $first_new_found = TRUE;
- }
-
- if ($topic->comment_count > 0) {
- $last_reply = new stdClass();
- $last_reply->created = $topic->last_comment_timestamp;
- $last_reply->name = $topic->last_comment_name;
- $last_reply->uid = $topic->last_comment_uid;
- $topic->last_reply = $last_reply;
- }
- $topics[$topic->nid] = $topic;
- }
-
- return $topics;
-}
-
-/**
* Implements hook_preprocess_HOOK() for block.html.twig.
*/
function forum_preprocess_block(&$variables) {
@@ -1250,61 +1003,6 @@ function template_preprocess_forum_submitted(&$variables) {
}
/**
- * Gets the last time the user viewed a node.
- *
- * @param $nid
- * The node ID.
- *
- * @return
- * The timestamp when the user last viewed this node, if the user has
- * previously viewed the node; otherwise HISTORY_READ_LIMIT.
- */
-function _forum_user_last_visit($nid) {
- global $user;
- $history = &drupal_static(__FUNCTION__, array());
-
- if (empty($history)) {
- $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid', array(':uid' => $user->uid));
- foreach ($result as $t) {
- $history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT;
- }
- }
- return isset($history[$nid]) ? $history[$nid] : HISTORY_READ_LIMIT;
-}
-
-/**
- * Gets topic sorting information based on an integer code.
- *
- * @param $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.
- *
- * @return
- * An array with the following values:
- * - field: A field for an SQL query.
- * - sort: 'asc' or 'desc'.
- */
-function _forum_get_topic_order($sortby) {
- switch ($sortby) {
- case 1:
- return array('field' => 'f.last_comment_timestamp', 'sort' => 'desc');
- break;
- case 2:
- return array('field' => 'f.last_comment_timestamp', 'sort' => 'asc');
- break;
- case 3:
- return array('field' => 'f.comment_count', 'sort' => 'desc');
- break;
- case 4:
- return array('field' => 'f.comment_count', 'sort' => 'asc');
- break;
- }
-}
-
-/**
* Updates the taxonomy index for a given node.
*
* @param $nid
diff --git a/core/modules/forum/forum.routing.yml b/core/modules/forum/forum.routing.yml
index f7d90f8..573f3da 100644
--- a/core/modules/forum/forum.routing.yml
+++ b/core/modules/forum/forum.routing.yml
@@ -10,3 +10,10 @@ forum_settings:
_form: '\Drupal\forum\ForumSettingsForm'
requirements:
_permission: 'administer forums'
+forum_page:
+ pattern: 'forum/{taxonomy_term}'
+ defaults:
+ taxonomy_term: ~
+ _content: 'Drupal\forum\Controller\ForumController::forumPage'
+ requirements:
+ _permission: 'access content'
diff --git a/core/modules/forum/forum.services.yml b/core/modules/forum/forum.services.yml
new file mode 100644
index 0000000..5ecc619
--- /dev/null
+++ b/core/modules/forum/forum.services.yml
@@ -0,0 +1,4 @@
+services:
+ forum_manager:
+ class: Drupal\forum\ForumManager
+ arguments: ['@config.factory', '@plugin.manager.entity', '@database']
diff --git a/core/modules/forum/lib/Drupal/forum/Controller/ForumController.php b/core/modules/forum/lib/Drupal/forum/Controller/ForumController.php
new file mode 100644
index 0000000..f10c8c8
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/Controller/ForumController.php
@@ -0,0 +1,166 @@
+get('config.factory'),
+ $container->get('forum_manager'),
+ $container->get('plugin.manager.entity')
+ );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param \Drupal\Core\Config\ConfigFactory $config_factory
+ * The config factory.
+ * @param \Drupal\forum\ForumManager $forum_manager
+ * The forum manager service.
+ * @param \Drupal\Core\Entity\EntityManager $entity_manager
+ * The entity manager service.
+ */
+ public function __construct(ConfigFactory $config_factory, ForumManager $forum_manager, EntityManager $entity_manager) {
+ $this->config = $config_factory->get('forum.settings');
+ $this->forumManager = $forum_manager;
+ $this->entityManager = $entity_manager;
+ }
+
+ /**
+ * Returns a forum page.
+ *
+ * @param \Drupal\forum\Plugin\Core\Entity\Forum $taxonomy_term
+ * (optional) The forum to render the page for. If empty, the forum index is
+ * rendered instead. Defaults to NULL.
+ *
+ * @return array
+ * A render array.
+ */
+ public function forumPage(Term $taxonomy_term = NULL) {
+ if ($taxonomy_term) {
+ return $this->forumForumPage($taxonomy_term);
+ }
+ // Index page.
+ return $this->forumIndex();
+ }
+
+ /**
+ * Returns forum page for a given forum.
+ *
+ * @param \Drupal\forum\Plugin\Core\Entity\Forum $taxonomy_term
+ * The forum to render the page for.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function forumForumPage(Term $taxonomy_term) {
+ // Get forum details
+ $taxonomy_term->forums = $this->forumManager->getChildren($this->config->get('vocabulary'), $taxonomy_term->id());
+ $taxonomy_term->parents = $this->forumManager->getParents($taxonomy_term->id());
+ // Page title.
+ drupal_set_title($taxonomy_term->label());
+ // Breadcrumb navigation.
+ $vocabularies = $this->entityManager->getStorageController('taxonomy_vocabulary')->load(array($this->config->get('vocabulary')));
+ $vocabulary = reset($vocabularies);
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb[] = l($vocabulary->label(), 'forum');
+ foreach (array_reverse($taxonomy_term->parents) as $parent) {
+ if ($parent->id() != $taxonomy_term->id()) {
+ $breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
+ }
+ }
+ drupal_set_breadcrumb($breadcrumb);
+ if ($taxonomy_term->container) {
+ // Add RSS feed for forums.
+ drupal_add_feed('taxonomy/term/' . $taxonomy_term->id() . '/feed', 'RSS - ' . $taxonomy_term->label());
+ }
+
+ if (!$taxonomy_term->container) {
+ $topics = $this->forumManager->getTopics($taxonomy_term->id());
+ }
+ else {
+ $topics = '';
+ }
+
+ $build = array(
+ '#theme' => 'forums',
+ '#forums' => $taxonomy_term->forums,
+ '#topics' => $topics,
+ '#parents' => $taxonomy_term->parents,
+ '#tid' => $taxonomy_term->id(),
+ '#sortby' => $this->config->get('topics.order'),
+ '#forums_per_page' => $this->config->get('topics.page_limit'),
+ );
+ $build['#attached']['css'][] = drupal_get_path('module', 'forum') . '/forum.css';
+ return $build;
+ }
+
+ /**
+ * Returns forum index page.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function forumIndex() {
+ $vocabularies = $this->entityManager->getStorageController('taxonomy_vocabulary')->load(array($this->config->get('vocabulary')));
+ $vocabulary = reset($vocabularies);
+ $index = $this->forumManager->getIndex();
+ // Set the page title to forum's vocabulary name.
+ drupal_set_title($vocabulary->label());
+ if (empty($index->forums)) {
+ // Root of empty forum.
+ drupal_set_title(t('No forums defined'));
+ }
+
+ $build = array(
+ '#theme' => 'forums',
+ '#forums' => $index->forums,
+ '#topics' => '',
+ '#parents' => array(),
+ '#tid' => $index->id(),
+ '#sortby' => $this->config->get('topics.order'),
+ '#forums_per_page' => $this->config->get('topics.page_limit'),
+ );
+ $build['#attached']['css'][] = drupal_get_path('module', 'forum') . '/forum.css';
+ return $build;
+ }
+
+}
diff --git a/core/modules/forum/lib/Drupal/forum/ForumManager.php b/core/modules/forum/lib/Drupal/forum/ForumManager.php
new file mode 100644
index 0000000..3892ccf
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/ForumManager.php
@@ -0,0 +1,469 @@
+config = $config_factory->get('forum.settings');
+ $this->entityManager = $entity_manager;
+ $this->database = $database;
+ }
+
+ /**
+ * Gets list of forum topics.
+ *
+ * @param int $tid
+ * Term id.
+ *
+ * @return array
+ * Array of topics.
+ */
+ public function getTopics($tid) {
+ $forum_per_page = $this->config->get('topics.page_limit');
+ $sortby = $this->config->get('topics.order');
+
+ global $user, $forum_topic_list_header;
+
+ $forum_topic_list_header = array(
+ array('data' => t('Topic'), 'field' => 'f.title'),
+ array('data' => t('Replies'), 'field' => 'f.comment_count'),
+ array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
+ );
+
+ $order = $this->getTopicOrder($sortby);
+ for ($i = 0; $i < count($forum_topic_list_header); $i++) {
+ if ($forum_topic_list_header[$i]['field'] == $order['field']) {
+ $forum_topic_list_header[$i]['sort'] = $order['sort'];
+ }
+ }
+
+ $query = $this->database->select('forum_index', 'f')
+ ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
+ ->extend('Drupal\Core\Database\Query\TableSortExtender');
+ $query->fields('f');
+ $query
+ ->condition('f.tid', $tid)
+ ->addTag('node_access')
+ ->addMetaData('base_table', 'forum_index')
+ ->orderBy('f.sticky', 'DESC')
+ ->orderByHeader($forum_topic_list_header)
+ ->limit($forum_per_page);
+
+ $count_query = $this->database->select('forum_index', 'f');
+ $count_query->condition('f.tid', $tid);
+ $count_query->addExpression('COUNT(*)');
+ $count_query->addTag('node_access');
+ $count_query->addMetaData('base_table', 'forum_index');
+
+ $query->setCountQuery($count_query);
+ $result = $query->execute();
+ $nids = array();
+ foreach ($result as $record) {
+ $nids[] = $record->nid;
+ }
+ if ($nids) {
+ $nodes = $this->entityManager->getStorageController('node')->load($nids);
+
+ $query = $this->database->select('node_field_data', 'n')
+ ->extend('Drupal\Core\Database\Query\TableSortExtender');
+ $query->fields('n', array('nid'));
+
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count'));
+
+ $query->join('forum_index', 'f', 'f.nid = ncs.nid');
+ $query->addField('f', 'tid', 'forum_tid');
+
+ $query->join('users', 'u', 'n.uid = u.uid');
+ $query->addField('u', 'name');
+
+ $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
+
+ $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
+
+ $query
+ ->orderBy('f.sticky', 'DESC')
+ ->orderByHeader($forum_topic_list_header)
+ ->condition('n.nid', $nids)
+ // @todo This should be actually filtering on the desired node language
+ // and just fall back to the default language.
+ ->condition('n.default_langcode', 1);
+
+ $result = array();
+ foreach ($query->execute() as $row) {
+ $topic = $nodes[$row->nid];
+ $topic->comment_mode = $topic->comment;
+
+ foreach ($row as $key => $value) {
+ $topic->{$key} = $value;
+ }
+ $result[] = $topic;
+ }
+ }
+ else {
+ $result = array();
+ }
+
+ $topics = array();
+ $first_new_found = FALSE;
+ foreach ($result as $topic) {
+ if ($user->uid) {
+ // A forum is new if the topic is new, or if there are new comments since
+ // the user's last visit.
+ if ($topic->forum_tid != $tid) {
+ $topic->new = 0;
+ }
+ else {
+ $history = $this->lastVisit($topic->nid);
+ // @todo move use comment service.
+ $topic->new_replies = $this->numberNew($topic->nid, $history);
+ $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
+ }
+ }
+ else {
+ // Do not track "new replies" status for topics if the user is anonymous.
+ $topic->new_replies = 0;
+ $topic->new = 0;
+ }
+
+ // Make sure only one topic is indicated as the first new topic.
+ $topic->first_new = FALSE;
+ if ($topic->new != 0 && !$first_new_found) {
+ $topic->first_new = TRUE;
+ $first_new_found = TRUE;
+ }
+
+ if ($topic->comment_count > 0) {
+ $last_reply = new \stdClass();
+ $last_reply->created = $topic->last_comment_timestamp;
+ $last_reply->name = $topic->last_comment_name;
+ $last_reply->uid = $topic->last_comment_uid;
+ $topic->last_reply = $last_reply;
+ }
+ $topics[$topic->nid] = $topic;
+ }
+
+ return $topics;
+
+ }
+
+ /**
+ * Gets topic sorting information based on an integer code.
+ *
+ * @param int $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.
+ *
+ * @return array
+ * An array with the following values:
+ * - field: A field for an SQL query.
+ * - sort: 'asc' or 'desc'.
+ */
+ protected function getTopicOrder($sortby) {
+ switch ($sortby) {
+ case 1:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'desc');
+ break;
+ case 2:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'asc');
+ break;
+ case 3:
+ return array('field' => 'f.comment_count', 'sort' => 'desc');
+ break;
+ case 4:
+ return array('field' => 'f.comment_count', 'sort' => 'asc');
+ break;
+ }
+ }
+
+ /**
+ * Util method to ensure comment_num_new is behind a method.
+ *
+ * @param int $nid
+ * Node id.
+ * @param int $timestamp
+ * Timestamp of last read.
+ *
+ * @return int
+ * Number of new comments.
+ */
+ protected function numberNew($nid, $timestamp) {
+ return comment_num_new($nid, $timestamp);
+ }
+
+ /**
+ * Gets the last time the user viewed a node.
+ *
+ * @param int $nid
+ * The node ID.
+ *
+ * @return int
+ * The timestamp when the user last viewed this node, if the user has
+ * previously viewed the node; otherwise HISTORY_READ_LIMIT.
+ */
+ protected function lastVisit($nid) {
+ global $user;
+
+ if (empty($this->history[$nid])) {
+ $result = $this->database->select('history', 'h')
+ ->fields('h', array('nid', 'timestamp'))
+ ->condition('uid', $user->uid)
+ ->execute();
+ foreach ($result as $t) {
+ $this->history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT;
+ }
+ }
+ return isset($this->history[$nid]) ? $this->history[$nid] : HISTORY_READ_LIMIT;
+ }
+
+ /**
+ * Utility method to get the last post information for the given forum tid.
+ *
+ * @param int $tid
+ * The forum tid.
+ *
+ * @return \stdClass
+ * The last post for the given forum.
+ */
+ protected function getLastPost($tid) {
+ if (!empty($this->lastPostData[$tid])) {
+ return $this->lastPostData[$tid];
+ }
+ // Query "Last Post" information for this forum.
+ $query = $this->database->select('node_field_data', 'n');
+ $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid));
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->join('users', 'u', 'ncs.last_comment_uid = u.uid');
+ $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u.name END', 'last_comment_name');
+
+ $topic = $query
+ ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
+ ->condition('n.status', 1)
+ ->orderBy('last_comment_timestamp', 'DESC')
+ ->range(0, 1)
+ ->addTag('node_access')
+ ->execute()
+ ->fetchObject();
+
+ // Build the last post information.
+ $last_post = new \stdClass();
+ if (!empty($topic->last_comment_timestamp)) {
+ $last_post->created = $topic->last_comment_timestamp;
+ $last_post->name = $topic->last_comment_name;
+ $last_post->uid = $topic->last_comment_uid;
+ }
+
+ $this->lastPostData[$tid] = $last_post;
+ return $last_post;
+ }
+
+ /**
+ * Utility method to fetch statistics for a forum.
+ *
+ * @param int $tid
+ * The forum tid.
+ *
+ * @return \stdClass|NULL
+ * Statistics for the given forum if statistics exist, else NULL
+ */
+ protected function getForumStatistics($tid) {
+ if (empty($this->forumStatistics)) {
+ // Prime the statistics.
+ $query = $this->database->select('node_field_data', 'n');
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->join('forum', 'f', 'n.vid = f.vid');
+ $query->addExpression('COUNT(n.nid)', 'topic_count');
+ $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
+ $this->forumStatistics = $query
+ ->fields('f', array('tid'))
+ ->condition('n.status', 1)
+ ->condition('n.default_langcode', 1)
+ ->groupBy('tid')
+ ->addTag('node_access')
+ ->execute()
+ ->fetchAllAssoc('tid');
+ }
+
+ if (!empty($this->forumStatistics[$tid])) {
+ return $this->forumStatistics[$tid];
+ }
+ }
+
+ /**
+ * Utility method to fetch the child forums for a given forum.
+ *
+ * @param int $vid
+ * The forum vocabulary id.
+ * @param int $tid
+ * The forum id to fetch the children for.
+ *
+ * @return array
+ * Array of children.
+ */
+ public function getChildren($vid, $tid) {
+ if (!empty($this->forumChildren[$tid])) {
+ return $this->forumChildren[$tid];
+ }
+ $forums = array();
+ $_forums = taxonomy_get_tree($vid, $tid, NULL, TRUE);
+ foreach ($_forums as $forum) {
+ // Determine if the child term is a container.
+ if (in_array($forum->id(), $this->config->get('containers'))) {
+ $forum->container = TRUE;
+ }
+
+ // Merge in the topic and post counters.
+ if (($count = $this->getForumStatistics($forum->id()))) {
+ $forum->num_topics = $count->topic_count;
+ $forum->num_posts = $count->topic_count + $count->comment_count;
+ }
+ else {
+ $forum->num_topics = 0;
+ $forum->num_posts = 0;
+ }
+
+ // Merge in last post details.
+ $forum->last_post = $this->getLastPost($forum->id());
+ $forums[$forum->id()] = $forum;
+ }
+
+ $this->forumChildren[$tid] = $forums;
+ return $forums;
+ }
+
+ /**
+ * Generates and returns the forum index.
+ *
+ * The forum index is a psuedo term that provides an overview of all forums.
+ *
+ * @return \Drupal\taxonomy\Plugin\Core\Entity\Term
+ * A psuedo term representing the overview of all forums.
+ */
+ public function getIndex() {
+ if ($this->index) {
+ return $index;
+ }
+
+ $vid = $this->config->get('vocabulary');
+ $index = $this->entityManager->getStorageController('taxonomy_term')->create(array(
+ 'tid' => 0,
+ 'container' => TRUE,
+ 'parents' => array(),
+ 'isIndex' => TRUE,
+ 'vid' => $vid
+ ));
+
+ // Load the tree below.
+ $index->forums = $this->getChildren($vid, 0);
+ $this->index = $index;
+ return $index;
+ }
+
+ /**
+ * Overrides \Drupal\Core\Entity\DatabaseStorageController::resetCache().
+ */
+ public function resetCache(array $ids = NULL) {
+ parent::resetCache($ids);
+ // Reset the index.
+ $this->index = NULL;
+ // Reset history.
+ $this->history = NULL;
+ }
+
+ /**
+ * Protected function to wrap call to taxonomy_term_load_parents_all.
+ *
+ * @param int $tid
+ * Term id.
+ *
+ * @return array
+ * Array of parent terms.
+ *
+ * @todo remove an inject a service when taxonomy_term_get_parents_all has an
+ * object-oriented equivalent.
+ */
+ public function getParents($tid) {
+ return taxonomy_term_load_parents_all($tid);
+ }
+
+}