diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 02fd704..4243780 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -1117,7 +1117,7 @@ function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_field
* Saved field UUIDs are set as keys in $skip_fields.
*/
function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
- if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
+ if ($entity->entityType() == 'node' && $entity->status && Drupal::service('forum_manager')->checkNodeType($entity)) {
$query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
foreach ($entity->taxonomy_forums as $language) {
foreach ($language as $delta) {
@@ -1154,7 +1154,7 @@ function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $enti
function hook_field_storage_pre_update(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
$first_call = &drupal_static(__FUNCTION__, array());
- if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
+ if ($entity->entityType() == 'node' && $entity->status && Drupal::service('forum_manager')->checkNodeType($entity)) {
// We don't maintain data for old revisions, so clear all previous values
// from the table. Since this hook runs once per field, per entity, make
// sure we only wipe values once.
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 11a1054..8f165f2 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -7,6 +7,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
+use Drupal\field\Field;
use Drupal\taxonomy\Plugin\Core\Entity\Term;
/**
@@ -93,18 +94,13 @@ 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_index',
);
- $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 +164,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_name) {
- if (($type = entity_load('node_type', $type_name)) && node_access('create', $type_name)) {
- $links[$type_name] = array(
- '#theme' => 'menu_local_action',
- '#link' => array(
- 'title' => t('Add new @node_type', array('@node_type' => $type->label())),
- 'href' => 'node/add/' . $type_name . '/' . $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 +238,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(
@@ -247,18 +247,26 @@ function forum_uri($forum) {
}
/**
- * Checks whether a node can be used in a forum, based on its content type.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- * A node entity.
+ * Implements hook_taxonomy_term_load().
*
- * @return
- * Boolean indicating if the node can be assigned to a forum.
+ * @param array $entities
+ * An array of taxonomy term entities, indexed by tid.
*/
-function _forum_node_check_node_type(EntityInterface $node) {
- // Fetch information about the forum field.
- $instance = field_info_instance('node', 'taxonomy_forums', $node->type);
- return !empty($instance);
+function forum_taxonomy_term_load(array $entities) {
+ $config = config('forum.settings');
+ $vid = $config->get('vocabulary');
+ foreach ($entities as $tid => $entity) {
+ if ($entity->bundle() != $vid) {
+ // Not a forum term.
+ continue;
+ }
+
+ // Determine if the requested term is a container.
+ if (in_array($entity->id(), $config->get('containers'))) {
+ $entity->container = TRUE;
+ }
+ }
+ return $entities;
}
/**
@@ -268,7 +276,7 @@ function _forum_node_check_node_type(EntityInterface $node) {
* forum taxonomy.
*/
function forum_node_validate(EntityInterface $node, $form) {
- if (_forum_node_check_node_type($node)) {
+ if (Drupal::service('forum_manager')->checkNodeType($node)) {
$langcode = $form['taxonomy_forums']['#language'];
// vocabulary is selected, not a "container" term.
if (!empty($node->taxonomy_forums[$langcode])) {
@@ -304,7 +312,7 @@ function forum_node_validate(EntityInterface $node, $form) {
* Assigns the forum taxonomy when adding a topic from within a forum.
*/
function forum_node_presave(EntityInterface $node) {
- if (_forum_node_check_node_type($node)) {
+ if (Drupal::service('forum_manager')->checkNodeType($node)) {
// Make sure all fields are set properly:
$node->icon = !empty($node->icon) ? $node->icon : '';
reset($node->taxonomy_forums);
@@ -327,7 +335,7 @@ function forum_node_presave(EntityInterface $node) {
* Implements hook_node_update().
*/
function forum_node_update(EntityInterface $node) {
- if (_forum_node_check_node_type($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.
if ($node->getRevisionId() == $node->original->getRevisionId() && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
@@ -377,7 +385,7 @@ function forum_node_update(EntityInterface $node) {
* Implements hook_node_insert().
*/
function forum_node_insert(EntityInterface $node) {
- if (_forum_node_check_node_type($node)) {
+ if (Drupal::service('forum_manager')->checkNodeType($node)) {
if (!empty($node->forum_tid)) {
$nid = db_insert('forum')
->fields(array(
@@ -394,7 +402,7 @@ function forum_node_insert(EntityInterface $node) {
* Implements hook_node_predelete().
*/
function forum_node_predelete(EntityInterface $node) {
- if (_forum_node_check_node_type($node)) {
+ if (Drupal::service('forum_manager')->checkNodeType($node)) {
db_delete('forum')
->condition('nid', $node->nid)
->execute();
@@ -410,7 +418,7 @@ function forum_node_predelete(EntityInterface $node) {
function forum_node_load($nodes) {
$node_vids = array();
foreach ($nodes as $node) {
- if (_forum_node_check_node_type($node)) {
+ if (Drupal::service('forum_manager')->checkNodeType($node)) {
$node_vids[] = $node->vid;
}
}
@@ -494,7 +502,7 @@ function forum_comment_delete($comment) {
* Implements hook_field_storage_pre_insert().
*/
function forum_field_storage_pre_insert(EntityInterface $entity, &$skip_fields) {
- if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
+ if ($entity->entityType() == 'node' && $entity->status && Drupal::service('forum_manager')->checkNodeType($entity)) {
$query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$translation = $entity->getTranslation($langcode, FALSE);
@@ -518,7 +526,7 @@ function forum_field_storage_pre_insert(EntityInterface $entity, &$skip_fields)
function forum_field_storage_pre_update(EntityInterface $entity, &$skip_fields) {
$first_call = &drupal_static(__FUNCTION__, array());
- if ($entity->entityType() == 'node' && _forum_node_check_node_type($entity)) {
+ if ($entity->entityType() == 'node' && Drupal::service('forum_manager')->checkNodeType($entity)) {
// If the node is published, update the forum index.
if ($entity->status) {
@@ -642,132 +650,6 @@ function forum_block_view_pre_render($elements) {
}
/**
- * 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.
@@ -798,147 +680,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) {
@@ -1204,61 +945,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..39ebaaa 100644
--- a/core/modules/forum/forum.routing.yml
+++ b/core/modules/forum/forum.routing.yml
@@ -4,9 +4,24 @@ forum_delete:
_form: 'Drupal\forum\Form\DeleteForm'
requirements:
_permission: 'administer forums'
+
forum_settings:
pattern: '/admin/structure/forum/settings'
defaults:
_form: '\Drupal\forum\ForumSettingsForm'
requirements:
_permission: 'administer forums'
+
+forum_index:
+ pattern: '/forum'
+ defaults:
+ _content: 'Drupal\forum\Controller\ForumController::forumIndex'
+ requirements:
+ _permission: 'access content'
+
+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
index 19033e8..a6f2c29 100644
--- a/core/modules/forum/forum.services.yml
+++ b/core/modules/forum/forum.services.yml
@@ -1,6 +1,9 @@
services:
+ forum_manager:
+ class: Drupal\forum\ForumManager
+ arguments: ['@config.factory', '@plugin.manager.entity', '@database', '@field.info']
forum.breadcrumb:
class: Drupal\forum\ForumBreadcrumbBuilder
- arguments: ['@plugin.manager.entity', '@config.factory']
+ arguments: ['@plugin.manager.entity', '@config.factory', '@forum_manager']
tags:
- { name: breadcrumb_builder, priority: 1001 }
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..87d5fa4
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/Controller/ForumController.php
@@ -0,0 +1,148 @@
+config = $config_factory->get('forum.settings');
+ $this->forumManager = $forum_manager;
+ $this->storageController = $storage_controller;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('forum_manager'),
+ $container->get('plugin.manager.entity')->getStorageController('taxonomy_vocabulary')
+ );
+ }
+
+ /**
+ * Returns forum page for a given forum.
+ *
+ * @param \Drupal\taxonomy\TermInterface $taxonomy_term
+ * The forum to render the page for.
+ *
+ * @return array
+ * A render array.
+ */
+ public function forumPage(TermInterface $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());
+ 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 = '';
+ }
+ return $this->build($taxonomy_term->forums, $taxonomy_term->id(), $topics, $taxonomy_term->parents);
+ }
+
+ /**
+ * Returns forum index page.
+ *
+ * @return array
+ * A render array.
+ */
+ public function forumIndex() {
+ $vocabularies = $this->storageController->load(array($this->config->get('vocabulary')));
+ $vocabulary = reset($vocabularies);
+ $index = $this->forumManager->getIndex();
+ if (empty($index->forums)) {
+ // Root of empty forum.
+ drupal_set_title(t('No forums defined'));
+ }
+ else {
+ // Set the page title to forum's vocabulary name.
+ drupal_set_title($vocabulary->label());
+ }
+ return $this->build($index->forums, $index->id());
+ }
+
+ /**
+ * Returns a renderable forum index page array.
+ *
+ * @param array $forums
+ * A list of forums.
+ * @param int $tid
+ * The taxonomy term of the forum.
+ * @param string $topics
+ * The topics of this forum.
+ * @param array $parents
+ * The parent forums in relation this forum.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function build($forums, $tid, $topics = '', $parents = array()) {
+ $build = array(
+ '#theme' => 'forums',
+ '#forums' => $forums,
+ '#topics' => $topics,
+ '#parents' => $parents,
+ '#tid' => $tid,
+ '#sortby' => $this->config->get('topics.order'),
+ '#forums_per_page' => $this->config->get('topics.page_limit'),
+ );
+ // @todo Make this a library - see https://drupal.org/node/2028113.
+ $build['#attached']['css'][] = drupal_get_path('module', 'forum') . '/forum.css';
+ return $build;
+ }
+
+}
diff --git a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
index 3bbb0df..4e82686 100644
--- a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
+++ b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
@@ -10,6 +10,7 @@
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityManager;
+use Drupal\forum\ForumManagerInterface;
/**
* Class to define the forum breadcrumb builder.
@@ -31,47 +32,47 @@ class ForumBreadcrumbBuilder implements BreadcrumbBuilderInterface {
protected $entityManager;
/**
+ * The forum manager service.
+ *
+ * @var \Drupal\forum\ForumManagerInterface
+ */
+ protected $forumManager;
+
+ /**
* Constructs a new ForumBreadcrumbBuilder.
*
* @param \Drupal\Core\Entity\EntityManager
* The entity manager.
* @param \Drupal\Core\Config\ConfigFactory $configFactory
* The configuration factory.
+ * @param \Drupal\forum\ForumManagerInterface $forum_manager
+ * The forum manager service.
*/
- public function __construct(EntityManager $entity_manager, ConfigFactory $configFactory) {
+ public function __construct(EntityManager $entity_manager, ConfigFactory $configFactory, ForumManagerInterface $forum_manager) {
$this->entityManager = $entity_manager;
$this->config = $configFactory->get('forum.settings');
+ $this->forumManager = $forum_manager;
}
/**
* {@inheritdoc}
*/
public function build(array $attributes) {
-
// @todo This only works for legacy routes. Once node/% and forum/% are
// converted to the new router this code will need to be updated.
- if (isset($attributes['drupal_menu_item'])) {
- $item = $attributes['drupal_menu_item'];
- switch ($item['path']) {
-
- case 'node/%':
- $node = $item['map'][1];
- // Load the object in case of missing wildcard loaders.
- $node = is_object($node) ? $node : node_load($node);
- if (_forum_node_check_node_type($node)) {
- $breadcrumb = $this->forumPostBreadcrumb($node);
- }
- break;
-
- case 'forum/%':
- $term = $item['map'][1];
- // Load the object in case of missing wildcard loaders.
- $term = is_object($term) ? $term : forum_forum_load($term);
- $breadcrumb = $this->forumTermBreadcrumb($term);
- break;
+ if (isset($attributes['drupal_menu_item']) && ($item = $attributes['drupal_menu_item']) && $item['path'] == 'node/%') {
+ $node = $item['map'][1];
+ // Load the object in case of missing wildcard loaders.
+ $node = is_object($node) ? $node : node_load($node);
+ if ($this->forumManager->checkNodeType($node)) {
+ $breadcrumb = $this->forumPostBreadcrumb($node);
}
}
+ if (!empty($attributes['_route']) && $attributes['_route'] == 'forum_page' && isset($attributes['taxonomy_term'])) {
+ $breadcrumb = $this->forumTermBreadcrumb($attributes['taxonomy_term']);
+ }
+
if (!empty($breadcrumb)) {
return $breadcrumb;
}
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..5186859
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/ForumManager.php
@@ -0,0 +1,489 @@
+configFactory = $config_factory;
+ $this->entityManager = $entity_manager;
+ $this->connection = $connection;
+ $this->fieldInfo = $field_info;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTopics($tid) {
+ $config = $this->configFactory->get('forum.settings');
+ $forum_per_page = $config->get('topics.page_limit');
+ $sortby = $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->connection->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->connection->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->conection->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:
+ * - ForumManager::NEWEST_FIRST: Date - newest first.
+ * - ForumManager::OLDEST_FIRST: Date - oldest first.
+ * - ForumManager::MOST_POPULAR_FIRST: Posts with the most comments first.
+ * - ForumManager::LEAST_POPULAR_FIRST: 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 static::NEWEST_FIRST:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'desc');
+
+ case static::OLDEST_FIRST:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'asc');
+
+ case static::MOST_POPULAR_FIRST:
+ return array('field' => 'f.comment_count', 'sort' => 'desc');
+
+ case static::LEAST_POPULAR_FIRST:
+ return array('field' => 'f.comment_count', 'sort' => 'asc');
+
+ }
+ }
+
+ /**
+ * Wraps comment_num_new() in 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->connection->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;
+ }
+
+ /**
+ * Provides 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->connection->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;
+ }
+
+ /**
+ * Provides 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->connection->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];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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->configFactory->get('forum.settings')->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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIndex() {
+ if ($this->index) {
+ return $index;
+ }
+
+ $vid = $this->configFactory->get('forum.settings')->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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resetCache() {
+ // Reset the index.
+ $this->index = NULL;
+ // Reset history.
+ $this->history = NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParents($tid) {
+ return taxonomy_term_load_parents_all($tid);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function checkNodeType(EntityInterface $node) {
+ // Fetch information about the forum field.
+ $instances = $this->fieldInfo->getBundleInstances('node', $node->bundle());
+ return !empty($instances['taxonomy_forums']);
+ }
+
+}
diff --git a/core/modules/forum/lib/Drupal/forum/ForumManagerInterface.php b/core/modules/forum/lib/Drupal/forum/ForumManagerInterface.php
new file mode 100644
index 0000000..8b5f7c4
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/ForumManagerInterface.php
@@ -0,0 +1,81 @@
+