Chris@0: 'entity.manager']; Chris@0: Chris@0: /** Chris@0: * The current database connection. Chris@0: * Chris@0: * @var \Drupal\Core\Database\Connection Chris@0: */ Chris@0: protected $database; Chris@0: Chris@0: /** Chris@18: * The replica database connection. Chris@18: * Chris@18: * @var \Drupal\Core\Database\Connection Chris@18: */ Chris@18: protected $databaseReplica; Chris@18: Chris@18: /** Chris@0: * The current logged in user. Chris@0: * Chris@0: * @var \Drupal\Core\Session\AccountInterface Chris@0: */ Chris@0: protected $currentUser; Chris@0: Chris@0: /** Chris@18: * The entity type manager. Chris@0: * Chris@18: * @var \Drupal\Core\Entity\EntityTypeManagerInterface Chris@0: */ Chris@18: protected $entityTypeManager; Chris@0: Chris@0: /** Chris@0: * The state service. Chris@0: * Chris@0: * @var \Drupal\Core\State\StateInterface Chris@0: */ Chris@0: protected $state; Chris@0: Chris@0: /** Chris@0: * Constructs the CommentStatistics service. Chris@0: * Chris@0: * @param \Drupal\Core\Database\Connection $database Chris@0: * The active database connection. Chris@0: * @param \Drupal\Core\Session\AccountInterface $current_user Chris@0: * The current logged in user. Chris@18: * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager Chris@18: * The entity type manager. Chris@0: * @param \Drupal\Core\State\StateInterface $state Chris@0: * The state service. Chris@18: * @param \Drupal\Core\Database\Connection|null $database_replica Chris@18: * (Optional) the replica database connection. Chris@0: */ Chris@18: public function __construct(Connection $database, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, StateInterface $state, Connection $database_replica = NULL) { Chris@0: $this->database = $database; Chris@18: $this->databaseReplica = $database_replica ?: $database; Chris@0: $this->currentUser = $current_user; Chris@18: $this->entityTypeManager = $entity_type_manager; Chris@0: $this->state = $state; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function read($entities, $entity_type, $accurate = TRUE) { Chris@18: $connection = $accurate ? $this->database : $this->databaseReplica; Chris@18: $stats = $connection->select('comment_entity_statistics', 'ces') Chris@0: ->fields('ces') Chris@0: ->condition('ces.entity_id', array_keys($entities), 'IN') Chris@0: ->condition('ces.entity_type', $entity_type) Chris@0: ->execute(); Chris@0: Chris@0: $statistics_records = []; Chris@0: while ($entry = $stats->fetchObject()) { Chris@0: $statistics_records[] = $entry; Chris@0: } Chris@0: return $statistics_records; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function delete(EntityInterface $entity) { Chris@0: $this->database->delete('comment_entity_statistics') Chris@0: ->condition('entity_id', $entity->id()) Chris@0: ->condition('entity_type', $entity->getEntityTypeId()) Chris@0: ->execute(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function create(FieldableEntityInterface $entity, $fields) { Chris@0: $query = $this->database->insert('comment_entity_statistics') Chris@0: ->fields([ Chris@0: 'entity_id', Chris@0: 'entity_type', Chris@0: 'field_name', Chris@0: 'cid', Chris@0: 'last_comment_timestamp', Chris@0: 'last_comment_name', Chris@0: 'last_comment_uid', Chris@0: 'comment_count', Chris@0: ]); Chris@0: foreach ($fields as $field_name => $detail) { Chris@0: // Skip fields that entity does not have. Chris@0: if (!$entity->hasField($field_name)) { Chris@0: continue; Chris@0: } Chris@0: // Get the user ID from the entity if it's set, or default to the Chris@0: // currently logged in user. Chris@0: $last_comment_uid = 0; Chris@0: if ($entity instanceof EntityOwnerInterface) { Chris@0: $last_comment_uid = $entity->getOwnerId(); Chris@0: } Chris@0: if (!isset($last_comment_uid)) { Chris@0: // Default to current user when entity does not implement Chris@0: // EntityOwnerInterface or author is not set. Chris@0: $last_comment_uid = $this->currentUser->id(); Chris@0: } Chris@0: // Default to REQUEST_TIME when entity does not have a changed property. Chris@0: $last_comment_timestamp = REQUEST_TIME; Chris@0: // @todo Make comment statistics language aware and add some tests. See Chris@0: // https://www.drupal.org/node/2318875 Chris@0: if ($entity instanceof EntityChangedInterface) { Chris@0: $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations(); Chris@0: } Chris@0: $query->values([ Chris@0: 'entity_id' => $entity->id(), Chris@0: 'entity_type' => $entity->getEntityTypeId(), Chris@0: 'field_name' => $field_name, Chris@0: 'cid' => 0, Chris@0: 'last_comment_timestamp' => $last_comment_timestamp, Chris@0: 'last_comment_name' => NULL, Chris@0: 'last_comment_uid' => $last_comment_uid, Chris@0: 'comment_count' => 0, Chris@0: ]); Chris@0: } Chris@0: $query->execute(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMaximumCount($entity_type) { Chris@0: return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', [':entity_type' => $entity_type])->fetchField(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getRankingInfo() { Chris@0: return [ Chris@0: 'comments' => [ Chris@0: 'title' => t('Number of comments'), Chris@0: 'join' => [ Chris@0: 'type' => 'LEFT', Chris@0: 'table' => 'comment_entity_statistics', Chris@0: 'alias' => 'ces', Chris@0: // Default to comment field as this is the most common use case for Chris@0: // nodes. Chris@0: 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_name = 'comment'", Chris@0: ], Chris@0: // Inverse law that maps the highest view count on the site to 1 and 0 Chris@0: // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite Chris@0: // in order to ensure that the :comment_scale argument is treated as Chris@0: // a numeric type, because the PostgreSQL PDO driver sometimes puts Chris@0: // values in as strings instead of numbers in complex expressions like Chris@0: // this. Chris@0: 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (ROUND(:comment_scale, 4)))', Chris@0: 'arguments' => [':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function update(CommentInterface $comment) { Chris@0: // Allow bulk updates and inserts to temporarily disable the maintenance of Chris@0: // the {comment_entity_statistics} table. Chris@0: if (!$this->state->get('comment.maintain_entity_statistics')) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $query = $this->database->select('comment_field_data', 'c'); Chris@0: $query->addExpression('COUNT(cid)'); Chris@0: $count = $query->condition('c.entity_id', $comment->getCommentedEntityId()) Chris@0: ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) Chris@0: ->condition('c.field_name', $comment->getFieldName()) Chris@0: ->condition('c.status', CommentInterface::PUBLISHED) Chris@0: ->condition('default_langcode', 1) Chris@0: ->execute() Chris@0: ->fetchField(); Chris@0: Chris@0: if ($count > 0) { Chris@0: // Comments exist. Chris@0: $last_reply = $this->database->select('comment_field_data', 'c') Chris@0: ->fields('c', ['cid', 'name', 'changed', 'uid']) Chris@0: ->condition('c.entity_id', $comment->getCommentedEntityId()) Chris@0: ->condition('c.entity_type', $comment->getCommentedEntityTypeId()) Chris@0: ->condition('c.field_name', $comment->getFieldName()) Chris@0: ->condition('c.status', CommentInterface::PUBLISHED) Chris@0: ->condition('default_langcode', 1) Chris@0: ->orderBy('c.created', 'DESC') Chris@0: ->range(0, 1) Chris@0: ->execute() Chris@0: ->fetchObject(); Chris@0: // Use merge here because entity could be created before comment field. Chris@0: $this->database->merge('comment_entity_statistics') Chris@0: ->fields([ Chris@0: 'cid' => $last_reply->cid, Chris@0: 'comment_count' => $count, Chris@0: 'last_comment_timestamp' => $last_reply->changed, Chris@0: 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name, Chris@0: 'last_comment_uid' => $last_reply->uid, Chris@0: ]) Chris@0: ->keys([ Chris@0: 'entity_id' => $comment->getCommentedEntityId(), Chris@0: 'entity_type' => $comment->getCommentedEntityTypeId(), Chris@0: 'field_name' => $comment->getFieldName(), Chris@0: ]) Chris@0: ->execute(); Chris@0: } Chris@0: else { Chris@0: // Comments do not exist. Chris@0: $entity = $comment->getCommentedEntity(); Chris@0: // Get the user ID from the entity if it's set, or default to the Chris@0: // currently logged in user. Chris@0: if ($entity instanceof EntityOwnerInterface) { Chris@0: $last_comment_uid = $entity->getOwnerId(); Chris@0: } Chris@0: if (!isset($last_comment_uid)) { Chris@0: // Default to current user when entity does not implement Chris@0: // EntityOwnerInterface or author is not set. Chris@0: $last_comment_uid = $this->currentUser->id(); Chris@0: } Chris@0: $this->database->update('comment_entity_statistics') Chris@0: ->fields([ Chris@0: 'cid' => 0, Chris@0: 'comment_count' => 0, Chris@0: // Use the changed date of the entity if it's set, or default to Chris@0: // REQUEST_TIME. Chris@0: 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME, Chris@0: 'last_comment_name' => '', Chris@0: 'last_comment_uid' => $last_comment_uid, Chris@0: ]) Chris@0: ->condition('entity_id', $comment->getCommentedEntityId()) Chris@0: ->condition('entity_type', $comment->getCommentedEntityTypeId()) Chris@0: ->condition('field_name', $comment->getFieldName()) Chris@0: ->execute(); Chris@0: } Chris@0: Chris@0: // Reset the cache of the commented entity so that when the entity is loaded Chris@0: // the next time, the statistics will be loaded again. Chris@18: $this->entityTypeManager->getStorage($comment->getCommentedEntityTypeId())->resetCache([$comment->getCommentedEntityId()]); Chris@0: } Chris@0: Chris@0: }