diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 0070378..a256698 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -11,7 +11,6 @@ */ use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityChangedInterface; use Drupal\comment\CommentInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\field\FieldInstanceConfigInterface; @@ -868,116 +867,6 @@ function comment_translation_configuration_element_submit($form, &$form_state) { } /** - * Implements hook_entity_load(). - * - * @see \Drupal\comment\Plugin\Field\FieldType\CommentItem::propertyDefinitions() - */ -function comment_entity_load($entities, $entity_type) { - if (!\Drupal::service('comment.manager')->getFields($entity_type)) { - // Do not query database when entity has no comment fields. - return; - } - // Load comment information from the database and update the entity's comment - // statistics properties, which are defined on each CommentItem field. - $result = db_select('comment_entity_statistics', 'ces') - ->fields('ces') - ->condition('ces.entity_id', array_keys($entities)) - ->condition('ces.entity_type', $entity_type) - ->execute(); - foreach ($result as $record) { - $parts = explode('__', $record->field_id, 2); - list(, $field_name) = $parts; - - // Skip fields that entity does not have. - if (!$entities[$record->entity_id]->hasField($field_name)) { - continue; - } - $comment_statistics = $entities[$record->entity_id]->get($field_name); - $comment_statistics->cid = $record->cid; - $comment_statistics->last_comment_timestamp = $record->last_comment_timestamp; - $comment_statistics->last_comment_name = $record->last_comment_name; - $comment_statistics->last_comment_uid = $record->last_comment_uid; - $comment_statistics->comment_count = $record->comment_count; - } -} - -/** - * Implements hook_entity_insert(). - */ -function comment_entity_insert(EntityInterface $entity) { - // Allow bulk updates and inserts to temporarily disable the - // maintenance of the {comment_entity_statistics} table. - if (\Drupal::state()->get('comment.maintain_entity_statistics') && - $fields = \Drupal::service('comment.manager')->getFields($entity->getEntityTypeId())) { - $query = db_insert('comment_entity_statistics') - ->fields(array( - 'entity_id', - 'entity_type', - 'field_id', - 'cid', - 'last_comment_timestamp', - 'last_comment_name', - 'last_comment_uid', - 'comment_count', - )); - $execute_query = FALSE; - foreach ($fields as $field_name => $detail) { - // Skip fields that entity does not have. - if (!$entity->hasField($field_name)) { - continue; - } - // There is at least one comment field, the query needs to be executed. - $execute_query = TRUE; - // Get the user ID from the entity if it is set, or default to the - // currently logged in user. - if ($entity instanceof EntityOwnerInterface) { - $last_comment_uid = $entity->getOwnerId(); - } - if (!isset($last_comment_uid)) { - // Default to current user when entity does not implement - // EntityOwnerInterface or author is not set. - $last_comment_uid = \Drupal::currentUser()->id(); - } - // Default to REQUEST_TIME when entity does not have a changed property. - $last_comment_timestamp = REQUEST_TIME; - if ($entity instanceof EntityChangedInterface) { - $last_comment_timestamp = $entity->getChangedTime(); - } - $query->values(array( - 'entity_id' => $entity->id(), - 'entity_type' => $entity->getEntityTypeId(), - 'field_id' => $entity->getEntityTypeId() . '__' . $field_name, - 'cid' => 0, - 'last_comment_timestamp' => $last_comment_timestamp, - 'last_comment_name' => NULL, - 'last_comment_uid' => $last_comment_uid, - 'comment_count' => 0, - )); - } - if ($execute_query) { - $query->execute(); - } - } -} - -/** - * Implements hook_entity_predelete(). - */ -function comment_entity_predelete(EntityInterface $entity) { - $cids = db_select('comment', 'c') - ->fields('c', array('cid')) - ->condition('entity_id', $entity->id()) - ->condition('entity_type', $entity->getEntityTypeId()) - ->execute() - ->fetchCol(); - entity_delete_multiple('comment', $cids); - db_delete('comment_entity_statistics') - ->condition('entity_id', $entity->id()) - ->condition('entity_type', $entity->getEntityTypeId()) - ->execute(); -} - -/** * Implements hook_node_update_index(). */ function comment_node_update_index(EntityInterface $node, $langcode) { @@ -1033,8 +922,8 @@ function comment_node_update_index(EntityInterface $node, $langcode) { */ function comment_cron() { // Store the maximum possible comments per thread (used for node search - // ranking by reply count). - \Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField())); + // ranking by reply count). Only supports default 'comment' field. + \Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, \Drupal::service('comment.statistics')->getMaximumCount())); } /** @@ -1536,22 +1425,7 @@ function _comment_per_page() { * Implements hook_ranking(). */ function comment_ranking() { - return array( - 'comments' => array( - 'title' => t('Number of comments'), - 'join' => array( - 'type' => 'LEFT', - 'table' => 'comment_entity_statistics', - 'alias' => 'ces', - // Default to comment field as this is the most common use case for - // nodes. - 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_id = 'node__comment'", - ), - // Inverse law that maps the highest reply count on the site to 1 and 0 to 0. - 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * :scale)', - 'arguments' => array(':scale' => (float) \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0), - ), - ); + return \Drupal::service('comment.statistics')->getRankingInfo(); } /** diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml index bcfb009..8524045 100644 --- a/core/modules/comment/comment.services.yml +++ b/core/modules/comment/comment.services.yml @@ -9,6 +9,10 @@ services: class: Drupal\comment\CommentManager arguments: ['@field.info', '@entity.manager', '@current_user', '@config.factory', '@string_translation', '@url_generator'] + comment.statistics: + class: Drupal\comment\CommentStatistics + arguments: ['@database', '@current_user', '@entity.manager', '@state'] + comment.route_enhancer: class: Drupal\comment\Routing\CommentBundleEnhancer arguments: ['@entity.manager'] diff --git a/core/modules/comment/lib/Drupal/comment/CommentStatistics.php b/core/modules/comment/lib/Drupal/comment/CommentStatistics.php new file mode 100644 index 0000000..2e3481c --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentStatistics.php @@ -0,0 +1,176 @@ +database = $database; + $this->currentUser = $current_user; + $this->entityManager = $entity_manager; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function getMaximumCount() { + return $this->database->query('SELECT MAX(comment_comment_count) FROM {node__comment}')->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function getRankingInfo() { + return array( + 'comments' => array( + 'title' => t('Number of comments'), + 'join' => array( + 'type' => 'LEFT', + // @todo Work out how to make this work beyond default comment field. + 'table' => 'node__comment', + 'alias' => 'ces', + // Default to comment field as this is the most common use case for + // nodes. + 'on' => "ces.entity_id = i.sid", + ), + // Inverse law that maps the highest reply count on the site to 1 and 0 + // to 0. + 'score' => '2.0 - 2.0 / (1.0 + ces.comment_comment_count * :scale)', + 'arguments' => array(':scale' => (float) $this->state->get('comment.node_comment_statistics_scale') ?: 0), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function update(CommentInterface $comment) { + // Allow bulk updates and inserts to temporarily disable the maintenance of + // the {comment_entity_statistics} table. + if (!$this->state->get('comment.maintain_entity_statistics')) { + return; + } + $query = $this->database->select('comment', 'c'); + $query->addExpression('COUNT(cid)'); + $count = $query->condition('c.entity_id', $comment->getCommentedEntityId()) + ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) + ->condition('c.field_id', $comment->getFieldId()) + ->condition('c.status', CommentInterface::PUBLISHED) + ->execute() + ->fetchField(); + + $field_name = $comment->getFieldName(); + $field = $this->entityManager->getStorageController('field_config')->load($comment->getCommentedEntityTypeId() . '.' . $field_name); + $table_name = FieldableDatabaseStorageController::_fieldTableName($field); + if ($count > 0) { + // Comments exist. + $last_reply = $this->database->select('comment', 'c') + ->fields('c', array('cid', 'name', 'changed', 'uid')) + ->condition('c.entity_id', $comment->getCommentedEntityId()) + ->condition('c.entity_type',$comment->getCommentedEntityTypeId()) + ->condition('c.field_id', $comment->getFieldId()) + ->condition('c.status', CommentInterface::PUBLISHED) + ->orderBy('c.created', 'DESC') + ->range(0, 1) + ->execute() + ->fetchObject(); + // Use merge here because entity could be created before comment field. + $this->database->merge($table_name) + ->fields(array( + $field_name . '_cid' => $last_reply->cid, + $field_name . '_comment_count' => $count, + $field_name . '_last_comment_timestamp' => $last_reply->changed, + $field_name . '_last_comment_name' => $last_reply->uid ? '' : $last_reply->name, + $field_name . '_last_comment_uid' => $last_reply->uid, + )) + ->key(array( + 'entity_id' => $comment->getCommentedEntityId(), + 'revision_id' => $comment->getCommentedEntityTypeId(), + )) + ->execute(); + } + else { + // Comments do not exist. + $entity = $comment->getCommentedEntity(); + if ($entity instanceof EntityOwnerInterface) { + $last_comment_uid = $entity->getOwnerId(); + } + if (!isset($last_comment_uid)) { + // Default to current user when entity does not implement + // EntityOwnerInterface or author is not set. + $last_comment_uid = $this->currentUser->id(); + } + $this->database->update($table_name) + ->fields(array( + $field_name . 'cid' => 0, + $field_name . 'comment_count' => 0, + // Use the created date of the entity if it's set, or default to + // REQUEST_TIME. + $field_name . 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME, + $field_name . 'last_comment_name' => '', + // Get the user ID from the entity if it's set, or default to the + // currently logged in user. + $field_name . 'last_comment_uid' => $last_comment_uid, + )) + ->condition('entity_id', $comment->getCommentedEntityId()) + ->execute(); + } + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php new file mode 100644 index 0000000..b4954e6 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php @@ -0,0 +1,48 @@ +statistics = $comment_statistics; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) { + return new static( + $entity_info, + $container->get('database'), + $container->get('field.info'), + $container->get('comment.statistics') + ); + } + + + /** * {@inheritdoc} */ protected function buildQuery($ids, $revision_id = FALSE) { @@ -47,77 +86,7 @@ protected function postLoad(array &$queried_entities) { * {@inheritdoc} */ public function updateEntityStatistics(CommentInterface $comment) { - // Allow bulk updates and inserts to temporarily disable the maintenance of - // the {comment_entity_statistics} table. - if (!\Drupal::state()->get('comment.maintain_entity_statistics')) { - return; - } - - $query = $this->database->select('comment', 'c'); - $query->addExpression('COUNT(cid)'); - $count = $query->condition('c.entity_id', $comment->getCommentedEntityId()) - ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) - ->condition('c.field_id', $comment->getFieldId()) - ->condition('c.status', CommentInterface::PUBLISHED) - ->execute() - ->fetchField(); - - if ($count > 0) { - // Comments exist. - $last_reply = $this->database->select('comment', 'c') - ->fields('c', array('cid', 'name', 'changed', 'uid')) - ->condition('c.entity_id', $comment->getCommentedEntityId()) - ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) - ->condition('c.field_id', $comment->getFieldId()) - ->condition('c.status', CommentInterface::PUBLISHED) - ->orderBy('c.created', 'DESC') - ->range(0, 1) - ->execute() - ->fetchObject(); - // Use merge here because entity could be created before comment field. - $this->database->merge('comment_entity_statistics') - ->fields(array( - 'cid' => $last_reply->cid, - 'comment_count' => $count, - 'last_comment_timestamp' => $last_reply->changed, - 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name, - 'last_comment_uid' => $last_reply->uid, - )) - ->key(array( - 'entity_id' => $comment->getCommentedEntityId(), - 'entity_type' => $comment->getCommentedEntityTypeId(), - 'field_id' => $comment->getFieldId(), - )) - ->execute(); - } - else { - // Comments do not exist. - $entity = $comment->getCommentedEntity(); - // Get the user ID from the entity if it's set, or default to the - // currently logged in user. - if ($entity instanceof EntityOwnerInterface) { - $last_comment_uid = $entity->getOwnerId(); - } - if (!isset($last_comment_uid)) { - // Default to current user when entity does not implement - // EntityOwnerInterface or author is not set. - $last_comment_uid = \Drupal::currentUser()->id(); - } - $this->database->update('comment_entity_statistics') - ->fields(array( - 'cid' => 0, - 'comment_count' => 0, - // Use the created date of the entity if it's set, or default to - // REQUEST_TIME. - 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME, - 'last_comment_name' => '', - 'last_comment_uid' => $last_comment_uid, - )) - ->condition('entity_id', $comment->getCommentedEntityId()) - ->condition('entity_type', $comment->getCommentedEntityTypeId()) - ->condition('field_id', $comment->getFieldId()) - ->execute(); - } + $this->statistics->update($comment); } /** diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldType/CommentItem.php b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldType/CommentItem.php index b50bafc..5a11ce3 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldType/CommentItem.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldType/CommentItem.php @@ -72,9 +72,51 @@ public static function schema(FieldDefinitionInterface $field_definition) { 'not null' => TRUE, 'default' => 0, ), + 'cid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The {comment}.cid of the last comment.', + ), + 'last_comment_timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The Unix timestamp of the last comment that was posted within this node, from {comment}.changed.', + ), + 'last_comment_name' => array( + 'type' => 'varchar', + 'length' => 60, + 'not null' => FALSE, + 'description' => 'The name of the latest author to post a comment on this node, from {comment}.name.', + ), + 'last_comment_uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The user ID of the latest author to post a comment on this node, from {comment}.uid.', + ), + 'comment_count' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The total number of comments on this entity.', + ), + ), + 'indexes' => array( + 'last_comment_timestamp' => array('last_comment_timestamp'), + 'comment_count' => array('comment_count'), + 'last_comment_uid' => array('last_comment_uid'),), + 'foreign keys' => array( + 'last_comment_author' => array( + 'table' => 'users', + 'columns' => array( + 'last_comment_uid' => 'uid', + ), + ), ), - 'indexes' => array(), - 'foreign keys' => array(), ); }