3 namespace Drupal\comment;
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Entity\FieldableEntityInterface;
7 use Drupal\Core\Entity\EntityChangedInterface;
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Entity\EntityManagerInterface;
10 use Drupal\Core\State\StateInterface;
11 use Drupal\Core\Session\AccountInterface;
12 use Drupal\user\EntityOwnerInterface;
14 class CommentStatistics implements CommentStatisticsInterface {
17 * The current database connection.
19 * @var \Drupal\Core\Database\Connection
24 * The current logged in user.
26 * @var \Drupal\Core\Session\AccountInterface
28 protected $currentUser;
31 * The entity manager service.
33 * @var \Drupal\Core\Entity\EntityManagerInterface
35 protected $entityManager;
40 * @var \Drupal\Core\State\StateInterface
45 * Constructs the CommentStatistics service.
47 * @param \Drupal\Core\Database\Connection $database
48 * The active database connection.
49 * @param \Drupal\Core\Session\AccountInterface $current_user
50 * The current logged in user.
51 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
52 * The entity manager service.
53 * @param \Drupal\Core\State\StateInterface $state
56 public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state) {
57 $this->database = $database;
58 $this->currentUser = $current_user;
59 $this->entityManager = $entity_manager;
60 $this->state = $state;
66 public function read($entities, $entity_type, $accurate = TRUE) {
67 $options = $accurate ? [] : ['target' => 'replica'];
68 $stats = $this->database->select('comment_entity_statistics', 'ces', $options)
70 ->condition('ces.entity_id', array_keys($entities), 'IN')
71 ->condition('ces.entity_type', $entity_type)
74 $statistics_records = [];
75 while ($entry = $stats->fetchObject()) {
76 $statistics_records[] = $entry;
78 return $statistics_records;
84 public function delete(EntityInterface $entity) {
85 $this->database->delete('comment_entity_statistics')
86 ->condition('entity_id', $entity->id())
87 ->condition('entity_type', $entity->getEntityTypeId())
94 public function create(FieldableEntityInterface $entity, $fields) {
95 $query = $this->database->insert('comment_entity_statistics')
101 'last_comment_timestamp',
106 foreach ($fields as $field_name => $detail) {
107 // Skip fields that entity does not have.
108 if (!$entity->hasField($field_name)) {
111 // Get the user ID from the entity if it's set, or default to the
112 // currently logged in user.
113 $last_comment_uid = 0;
114 if ($entity instanceof EntityOwnerInterface) {
115 $last_comment_uid = $entity->getOwnerId();
117 if (!isset($last_comment_uid)) {
118 // Default to current user when entity does not implement
119 // EntityOwnerInterface or author is not set.
120 $last_comment_uid = $this->currentUser->id();
122 // Default to REQUEST_TIME when entity does not have a changed property.
123 $last_comment_timestamp = REQUEST_TIME;
124 // @todo Make comment statistics language aware and add some tests. See
125 // https://www.drupal.org/node/2318875
126 if ($entity instanceof EntityChangedInterface) {
127 $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
130 'entity_id' => $entity->id(),
131 'entity_type' => $entity->getEntityTypeId(),
132 'field_name' => $field_name,
134 'last_comment_timestamp' => $last_comment_timestamp,
135 'last_comment_name' => NULL,
136 'last_comment_uid' => $last_comment_uid,
137 'comment_count' => 0,
146 public function getMaximumCount($entity_type) {
147 return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', [':entity_type' => $entity_type])->fetchField();
153 public function getRankingInfo() {
156 'title' => t('Number of comments'),
159 'table' => 'comment_entity_statistics',
161 // Default to comment field as this is the most common use case for
163 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_name = 'comment'",
165 // Inverse law that maps the highest view count on the site to 1 and 0
166 // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
167 // in order to ensure that the :comment_scale argument is treated as
168 // a numeric type, because the PostgreSQL PDO driver sometimes puts
169 // values in as strings instead of numbers in complex expressions like
171 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (ROUND(:comment_scale, 4)))',
172 'arguments' => [':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0],
180 public function update(CommentInterface $comment) {
181 // Allow bulk updates and inserts to temporarily disable the maintenance of
182 // the {comment_entity_statistics} table.
183 if (!$this->state->get('comment.maintain_entity_statistics')) {
187 $query = $this->database->select('comment_field_data', 'c');
188 $query->addExpression('COUNT(cid)');
189 $count = $query->condition('c.entity_id', $comment->getCommentedEntityId())
190 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
191 ->condition('c.field_name', $comment->getFieldName())
192 ->condition('c.status', CommentInterface::PUBLISHED)
193 ->condition('default_langcode', 1)
199 $last_reply = $this->database->select('comment_field_data', 'c')
200 ->fields('c', ['cid', 'name', 'changed', 'uid'])
201 ->condition('c.entity_id', $comment->getCommentedEntityId())
202 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
203 ->condition('c.field_name', $comment->getFieldName())
204 ->condition('c.status', CommentInterface::PUBLISHED)
205 ->condition('default_langcode', 1)
206 ->orderBy('c.created', 'DESC')
210 // Use merge here because entity could be created before comment field.
211 $this->database->merge('comment_entity_statistics')
213 'cid' => $last_reply->cid,
214 'comment_count' => $count,
215 'last_comment_timestamp' => $last_reply->changed,
216 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
217 'last_comment_uid' => $last_reply->uid,
220 'entity_id' => $comment->getCommentedEntityId(),
221 'entity_type' => $comment->getCommentedEntityTypeId(),
222 'field_name' => $comment->getFieldName(),
227 // Comments do not exist.
228 $entity = $comment->getCommentedEntity();
229 // Get the user ID from the entity if it's set, or default to the
230 // currently logged in user.
231 if ($entity instanceof EntityOwnerInterface) {
232 $last_comment_uid = $entity->getOwnerId();
234 if (!isset($last_comment_uid)) {
235 // Default to current user when entity does not implement
236 // EntityOwnerInterface or author is not set.
237 $last_comment_uid = $this->currentUser->id();
239 $this->database->update('comment_entity_statistics')
242 'comment_count' => 0,
243 // Use the changed date of the entity if it's set, or default to
245 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
246 'last_comment_name' => '',
247 'last_comment_uid' => $last_comment_uid,
249 ->condition('entity_id', $comment->getCommentedEntityId())
250 ->condition('entity_type', $comment->getCommentedEntityTypeId())
251 ->condition('field_name', $comment->getFieldName())
255 // Reset the cache of the commented entity so that when the entity is loaded
256 // the next time, the statistics will be loaded again.
257 $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache([$comment->getCommentedEntityId()]);