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..67ab628 --- /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(NodeInterface $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 @@ +