diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index ca1e585..f5199f7 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -194,6 +194,9 @@ function comment_field_instance_config_delete(FieldInstanceConfigInterface $inst $entity_query->condition('field_name', $instance->getName()); $cids = $entity_query->execute(); entity_delete_multiple('comment', $cids); + // TODO delete comment statistics. After #2338457 gets fixed we may have a + // list of entity IDs here, to pass to CommentStatistics::delete(). + // Until then: unimportant, since the above always deletes 0 comments. } } @@ -423,9 +426,9 @@ function comment_entity_storage_load($entities, $entity_type) { 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())) { - \Drupal::service('comment.statistics')->create($entity, array_keys($fields)); + if (\Drupal::state()->get('comment.maintain_entity_statistics') + && $entity->getEntityType()->isFieldable()) { + \Drupal::service('comment.statistics')->create($entity); } } @@ -445,7 +448,7 @@ function comment_entity_predelete(EntityInterface $entity) { $entity_query->condition('entity_type', $entity->getEntityTypeId()); $cids = $entity_query->execute(); entity_delete_multiple('comment', $cids); - \Drupal::service('comment.statistics')->delete($entity); + \Drupal::service('comment.statistics')->delete(array($entity->id()), $entity->getEntityTypeId()); } } diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php index f9ac89a..3833348 100644 --- a/core/modules/comment/src/CommentStatistics.php +++ b/core/modules/comment/src/CommentStatistics.php @@ -6,11 +6,9 @@ namespace Drupal\comment; - use Drupal\Core\Database\Connection; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Session\AccountInterface; @@ -69,36 +67,68 @@ public function __construct(Connection $database, AccountInterface $current_user * {@inheritdoc} */ public function read(array $entities, $entity_type, array $field_names = array(), $accurate = TRUE) { - $options = $accurate ? array() : array('target' => 'replica'); - $query = $this->database->select('comment_entity_statistics', 'ces', $options) - ->fields('ces') - ->condition('ces.entity_id', array_keys($entities)) - ->condition('ces.entity_type', $entity_type); - if ($field_names) { - $query->condition('ces.field_name', $field_names, 'IN'); - } + if ($entities) { + $options = $accurate ? array() : array('target' => 'replica'); + $query = $this->database->select('comment_entity_statistics', 'ces', $options) + ->fields('ces') + ->condition('ces.entity_id', array_keys($entities)) + ->condition('ces.entity_type', $entity_type); + if ($field_names) { + $query->condition('ces.field_name', $field_names, 'IN'); + } - return $query->execute(); + return $query->execute(); + } + return array(); } /** * {@inheritdoc} */ - public function delete(ContentEntityInterface $entity, array $field_names = array()) { - $query = $this->database->delete('comment_entity_statistics') - ->condition('entity_id', $entity->id()) - ->condition('entity_type', $entity->getEntityTypeId()); - if ($field_names) { - $query->condition('field_name', $field_names, 'IN'); + public function delete(array $entity_ids, $entity_type, array $field_names = array()) { + if ($entity_ids && $entity_type) { + $query = $this->database->delete('comment_entity_statistics') + ->condition('entity_id', $entity_ids, 'IN') + ->condition('entity_type', $entity_type); + if ($field_names) { + $query->condition('field_name', $field_names, 'IN'); + } + $query->execute(); } - - $query->execute(); } /** * {@inheritdoc} */ - public function create(ContentEntityInterface $entity, array $field_names) { + public function create(ContentEntityInterface $entity, array $field_names = array()) { + if (!$field_names) { + $map = $this->entityManager->getFieldMapByFieldType('comment'); + $entity_type = $entity->getEntityTypeId(); + if (!isset($map[$entity_type])) { + return; + } + $field_names = array_keys($map[$entity_type]); + // This means $field_names are attached to this entity type, but we still + // need to check this for the specific entity('s bundle). + } + + // Get the user ID from the entity if it's set, or default to the + // currently logged in user. + $last_comment_uid = 0; + 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(); + } + // 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 = $this->database->insert('comment_entity_statistics') ->fields(array( 'entity_id', @@ -110,27 +140,12 @@ public function create(ContentEntityInterface $entity, array $field_names) { 'last_comment_uid', 'comment_count', )); + $row_added = FALSE; foreach ($field_names as $field_name) { // Skip fields that entity does not have. if (!$entity->hasField($field_name)) { continue; } - // Get the user ID from the entity if it's set, or default to the - // currently logged in user. - $last_comment_uid = 0; - 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(); - } - // 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(), @@ -141,8 +156,11 @@ public function create(ContentEntityInterface $entity, array $field_names) { 'last_comment_uid' => $last_comment_uid, 'comment_count' => 0, )); + $row_added = TRUE; + } + if ($row_added) { + $query->execute(); } - $query->execute(); } /** @@ -180,84 +198,100 @@ public function getRankingInfo() { /** * {@inheritdoc} */ - public function update(CommentInterface $comment) { + public function update(ContentEntityInterface $entity, array $field_names = array()) { // 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_field_data', 'c'); - $query->addExpression('COUNT(cid)'); - $count = $query->condition('c.entity_id', $comment->getCommentedEntityId()) - ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) - ->condition('c.field_name', $comment->getFieldName()) - ->condition('c.status', CommentInterface::PUBLISHED) - ->condition('default_langcode', 1) - ->execute() - ->fetchField(); + if (!$field_names) { + $map = $this->entityManager->getFieldMapByFieldType('comment'); + $entity_type = $entity->getEntityTypeId(); + if (!isset($map[$entity_type])) { + return; + } + $field_names = array_keys($map[$entity_type]); + // This means $field_names are attached to this entity type, but we still + // need to check this for the specific entity('s bundle). + } - if ($count > 0) { - // Comments exist. - $last_reply = $this->database->select('comment_field_data', 'c') - ->fields('c', array('cid', 'name', 'changed', 'uid')) - ->condition('c.entity_id', $comment->getCommentedEntityId()) - ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) - ->condition('c.field_name', $comment->getFieldName()) + foreach ($field_names as $field_name) { + if (!$entity->hasField($field_name)) { + continue; + } + + $query = $this->database->select('comment_field_data', 'c'); + $query->addExpression('COUNT(cid)'); + $count = $query->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->getEntityTypeId()) + ->condition('c.field_name', $field_name) ->condition('c.status', CommentInterface::PUBLISHED) ->condition('default_langcode', 1) - ->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, - )) - ->keys(array( - 'entity_id' => $comment->getCommentedEntityId(), - 'entity_type' => $comment->getCommentedEntityTypeId(), - 'field_name' => $comment->getFieldName(), - )) - ->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(); + ->fetchField(); + + if ($count > 0) { + // Comments exist. + $last_reply = $this->database->select('comment_field_data', 'c') + ->fields('c', array('cid', 'name', 'changed', 'uid')) + ->condition('c.entity_id', $entity->id()) + ->condition('c.entity_type', $entity->getEntityTypeId()) + ->condition('c.field_name', $field_name) + ->condition('c.status', CommentInterface::PUBLISHED) + ->condition('default_langcode', 1) + ->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, + )) + ->keys(array( + 'entity_id' => $entity->id(), + 'entity_type' => $entity->getEntityTypeId(), + 'field_name' => $field_name, + )) + ->execute(); } - 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(); + else { + // Comments do not exist. + // 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 = $this->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', $entity->id()) + ->condition('entity_type', $entity->getEntityTypeId()) + ->condition('field_name', $field_name) + ->execute(); } - $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_name', $comment->getFieldName()) - ->execute(); } // Reset the cache of the commented entity so that when the entity is loaded // the next time, the statistics will be loaded again. - $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache(array($comment->getCommentedEntityId())); + $this->entityManager->getStorage($entity->getEntityTypeId())->resetCache(array($entity->id())); } } diff --git a/core/modules/comment/src/CommentStatisticsInterface.php b/core/modules/comment/src/CommentStatisticsInterface.php index 3c5959b..87ee5a8 100644 --- a/core/modules/comment/src/CommentStatisticsInterface.php +++ b/core/modules/comment/src/CommentStatisticsInterface.php @@ -58,21 +58,26 @@ public function read(array $entities, $entity_type, array $field_names = array() /** * Delete comment statistics records for an entity. * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity for which comment statistics should be deleted. + * @param array $entity_ids + * Entity IDs + * @param string $entity_type + * The entity type of the passed entities. * @param array $field_names * The name(s) of the comment field(s) whose statistics records to delete. - * If not specified, all records for the entity will be deleted. + * If not specified, all records for the specified ids will be deleted. */ - public function delete(ContentEntityInterface $entity, array $field_names = array()); + public function delete(array $entity_ids, $entity_type, array $field_names = array()); /** - * Update or insert comment statistics records after a comment is added. + * Update or insert comment statistics records. * - * @param \Drupal\comment\CommentInterface $comment - * The comment added or updated. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity for which comment statistics should be updated. + * @param array $field_names + * The name(s) of the comment field(s) whose statistics records to updated. + * If not specified, all records for the entity will be updated. */ - public function update(CommentInterface $comment); + public function update(ContentEntityInterface $entity, array $field_names = array()); /** * Find the maximum number of comments for the given entity type. @@ -90,7 +95,7 @@ public function update(CommentInterface $comment); public function getMaximumCount($entity_type); /** - * Insert an empty record for the given entity. + * Create empty comment statistics records. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The created entity for which a statistics record is to be initialized. @@ -98,6 +103,6 @@ public function getMaximumCount($entity_type); * The name(s) of the comment field(s) which are defined for the entity * type. Separate statistics records will be inserted for each field name. */ - public function create(ContentEntityInterface $entity, array $field_names); + public function create(ContentEntityInterface $entity, array $field_names = array()); } diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 9823f9f..152c407 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -148,7 +148,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { $this->releaseThreadLock(); // Update the {comment_entity_statistics} table prior to executing the hook. - \Drupal::service('comment.statistics')->update($this); + \Drupal::service('comment.statistics')->update($this->getCommentedEntity(), array($this->getFieldName())); } /** @@ -167,11 +167,27 @@ protected function releaseThreadLock() { public static function postDelete(EntityStorageInterface $storage, array $entities) { parent::postDelete($storage, $entities); + // Note our commented entities/fields. (Likely one, maybe more.) + $commented_entities = array(); + $commented_fields = array(); + foreach ($entities as $comment) { + $id = $comment->getCommentedEntityId(); + $field_name = $comment->getFieldName(); + + if (!isset($commented_fields[$id][$field_name])) { + $commented_fields[$id][$field_name] = $field_name; + + if (!isset($commented_entities[$id])) { + $commented_entities[$id] = $comment->getCommentedEntity(); + } + } + } + $child_cids = $storage->getChildCids($entities); entity_delete_multiple('comment', $child_cids); - foreach ($entities as $id => $entity) { - \Drupal::service('comment.statistics')->update($entity); + foreach ($commented_entities as $id => $commented_entity) { + \Drupal::service('comment.statistics')->update($commented_entity, $commented_fields[$id]); } } diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module index fba29f8..8d4558f 100644 --- a/core/modules/tracker/tracker.module +++ b/core/modules/tracker/tracker.module @@ -293,7 +293,7 @@ function _tracker_add($nid, $uid, $changed) { */ function _tracker_calculate_changed($node) { $changed = $node->getChangedTime(); - $latest_comment = \Drupal::service('comment.statistics')->read(array($node), 'node', FALSE); + $latest_comment = \Drupal::service('comment.statistics')->read(array($node), 'node', array(), FALSE); if ($latest_comment && $latest_comment->last_comment_timestamp > $changed) { $changed = $latest_comment->last_comment_timestamp; } diff --git a/core/modules/tracker/tracker.pages.inc b/core/modules/tracker/tracker.pages.inc index a88b754..587c555 100644 --- a/core/modules/tracker/tracker.pages.inc +++ b/core/modules/tracker/tracker.pages.inc @@ -49,7 +49,7 @@ function tracker_page($account = NULL) { $nodes = entity_load_multiple('node', array_keys($tracker_data)); // Enrich the node data. - $result = \Drupal::service('comment.statistics')->read($nodes, 'node', FALSE); + $result = \Drupal::service('comment.statistics')->read($nodes, 'node', array(), FALSE); foreach ($result as $statistics) { // The node ID may not be unique; there can be multiple comment fields. // Make comment_count the total of all comments.