diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 42d7326..4c03884 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_forum_page', ); $items['admin/structure/forum'] = array( 'title' => 'Forums', @@ -118,6 +111,7 @@ function forum_menu() { 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, ); + // @todo route and form controllerify this. $items['admin/structure/forum/add/container'] = array( 'title' => 'Add container', 'page callback' => 'forum_form_main', @@ -127,6 +121,7 @@ function forum_menu() { 'parent' => 'admin/structure/forum', 'file' => 'forum.admin.inc', ); + // @todo route and form controllerify this. $items['admin/structure/forum/add/forum'] = array( 'title' => 'Add forum', 'page callback' => 'forum_form_main', @@ -136,6 +131,7 @@ function forum_menu() { 'parent' => 'admin/structure/forum', 'file' => 'forum.admin.inc', ); + // @todo route and form controllerify this. $items['admin/structure/forum/settings'] = array( 'title' => 'Settings', 'weight' => 100, @@ -143,6 +139,7 @@ function forum_menu() { 'parent' => 'admin/structure/forum', 'route_name' => 'forum_settings', ); + // @todo route and form controllerify this. $items['admin/structure/forum/edit/container/%taxonomy_term'] = array( 'title' => 'Edit container', 'page callback' => 'forum_form_main', @@ -150,6 +147,7 @@ function forum_menu() { 'access arguments' => array('administer forums'), 'file' => 'forum.admin.inc', ); + // @todo route and form controllerify this. $items['admin/structure/forum/edit/forum/%taxonomy_term'] = array( 'title' => 'Edit forum', 'page callback' => 'forum_form_main', @@ -168,48 +166,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 = config('forum.settings')->get('vocabulary'); + $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, + ), + ); + 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 +240,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( @@ -285,6 +287,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 +713,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 +743,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 +1008,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..2c3178d 100644 --- a/core/modules/forum/forum.routing.yml +++ b/core/modules/forum/forum.routing.yml @@ -10,3 +10,15 @@ forum_settings: _form: '\Drupal\forum\ForumSettingsForm' requirements: _permission: 'administer forums' +forum_page: + pattern: 'forum' + defaults: + _content: 'Drupal\forum\Controller\ForumController::forumPage' + requirements: + _permission: 'access content' +forum_forum_page: + pattern: 'forum/{taxonomy_term}' + defaults: + _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..6b5f181 --- /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. + */ + 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); + } + +}