3 namespace Drupal\comment;
6 use Drupal\Core\Database\Connection;
7 use Drupal\Core\Entity\FieldableEntityInterface;
8 use Drupal\Core\Entity\EntityChangedInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\EntityManagerInterface;
11 use Drupal\Core\State\StateInterface;
12 use Drupal\Core\Session\AccountInterface;
13 use Drupal\user\EntityOwnerInterface;
15 class CommentStatistics implements CommentStatisticsInterface {
18 * The current database connection.
20 * @var \Drupal\Core\Database\Connection
25 * The current logged in user.
27 * @var \Drupal\Core\Session\AccountInterface
29 protected $currentUser;
32 * The entity manager service.
34 * @var \Drupal\Core\Entity\EntityManagerInterface
36 protected $entityManager;
41 * @var \Drupal\Core\State\StateInterface
46 * Constructs the CommentStatistics service.
48 * @param \Drupal\Core\Database\Connection $database
49 * The active database connection.
50 * @param \Drupal\Core\Session\AccountInterface $current_user
51 * The current logged in user.
52 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
53 * The entity manager service.
54 * @param \Drupal\Core\State\StateInterface $state
57 public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state) {
58 $this->database = $database;
59 $this->currentUser = $current_user;
60 $this->entityManager = $entity_manager;
61 $this->state = $state;
67 public function read($entities, $entity_type, $accurate = TRUE) {
68 $options = $accurate ? [] : ['target' => 'replica'];
69 $stats = $this->database->select('comment_entity_statistics', 'ces', $options)
71 ->condition('ces.entity_id', array_keys($entities), 'IN')
72 ->condition('ces.entity_type', $entity_type)
75 $statistics_records = [];
76 while ($entry = $stats->fetchObject()) {
77 $statistics_records[] = $entry;
79 return $statistics_records;
85 public function delete(EntityInterface $entity) {
86 $this->database->delete('comment_entity_statistics')
87 ->condition('entity_id', $entity->id())
88 ->condition('entity_type', $entity->getEntityTypeId())
95 public function create(FieldableEntityInterface $entity, $fields) {
96 $query = $this->database->insert('comment_entity_statistics')
102 'last_comment_timestamp',
107 foreach ($fields as $field_name => $detail) {
108 // Skip fields that entity does not have.
109 if (!$entity->hasField($field_name)) {
112 // Get the user ID from the entity if it's set, or default to the
113 // currently logged in user.
114 $last_comment_uid = 0;
115 if ($entity instanceof EntityOwnerInterface) {
116 $last_comment_uid = $entity->getOwnerId();
118 if (!isset($last_comment_uid)) {
119 // Default to current user when entity does not implement
120 // EntityOwnerInterface or author is not set.
121 $last_comment_uid = $this->currentUser->id();
123 // Default to REQUEST_TIME when entity does not have a changed property.
124 $last_comment_timestamp = REQUEST_TIME;
125 // @todo Make comment statistics language aware and add some tests. See
126 // https://www.drupal.org/node/2318875
127 if ($entity instanceof EntityChangedInterface) {
128 $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
131 'entity_id' => $entity->id(),
132 'entity_type' => $entity->getEntityTypeId(),
133 'field_name' => $field_name,
135 'last_comment_timestamp' => $last_comment_timestamp,
136 'last_comment_name' => NULL,
137 'last_comment_uid' => $last_comment_uid,
138 'comment_count' => 0,
147 public function getMaximumCount($entity_type) {
148 return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', [':entity_type' => $entity_type])->fetchField();
154 public function getRankingInfo() {
157 'title' => t('Number of comments'),
160 'table' => 'comment_entity_statistics',
162 // Default to comment field as this is the most common use case for
164 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_name = 'comment'",
166 // Inverse law that maps the highest view count on the site to 1 and 0
167 // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
168 // in order to ensure that the :comment_scale argument is treated as
169 // a numeric type, because the PostgreSQL PDO driver sometimes puts
170 // values in as strings instead of numbers in complex expressions like
172 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (ROUND(:comment_scale, 4)))',
173 'arguments' => [':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0],
181 public function update(CommentInterface $comment) {
182 // Allow bulk updates and inserts to temporarily disable the maintenance of
183 // the {comment_entity_statistics} table.
184 if (!$this->state->get('comment.maintain_entity_statistics')) {
188 $query = $this->database->select('comment_field_data', 'c');
189 $query->addExpression('COUNT(cid)');
190 $count = $query->condition('c.entity_id', $comment->getCommentedEntityId())
191 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
192 ->condition('c.field_name', $comment->getFieldName())
193 ->condition('c.status', CommentInterface::PUBLISHED)
194 ->condition('default_langcode', 1)
200 $last_reply = $this->database->select('comment_field_data', 'c')
201 ->fields('c', ['cid', 'name', 'changed', 'uid'])
202 ->condition('c.entity_id', $comment->getCommentedEntityId())
203 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
204 ->condition('c.field_name', $comment->getFieldName())
205 ->condition('c.status', CommentInterface::PUBLISHED)
206 ->condition('default_langcode', 1)
207 ->orderBy('c.created', 'DESC')
211 // Use merge here because entity could be created before comment field.
212 $this->database->merge('comment_entity_statistics')
214 'cid' => $last_reply->cid,
215 'comment_count' => $count,
216 'last_comment_timestamp' => $last_reply->changed,
217 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
218 'last_comment_uid' => $last_reply->uid,
221 'entity_id' => $comment->getCommentedEntityId(),
222 'entity_type' => $comment->getCommentedEntityTypeId(),
223 'field_name' => $comment->getFieldName(),
228 // Comments do not exist.
229 $entity = $comment->getCommentedEntity();
230 // Get the user ID from the entity if it's set, or default to the
231 // currently logged in user.
232 if ($entity instanceof EntityOwnerInterface) {
233 $last_comment_uid = $entity->getOwnerId();
235 if (!isset($last_comment_uid)) {
236 // Default to current user when entity does not implement
237 // EntityOwnerInterface or author is not set.
238 $last_comment_uid = $this->currentUser->id();
240 $this->database->update('comment_entity_statistics')
243 'comment_count' => 0,
244 // Use the changed date of the entity if it's set, or default to
246 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
247 'last_comment_name' => '',
248 'last_comment_uid' => $last_comment_uid,
250 ->condition('entity_id', $comment->getCommentedEntityId())
251 ->condition('entity_type', $comment->getCommentedEntityTypeId())
252 ->condition('field_name', $comment->getFieldName())
256 // Reset the cache of the commented entity so that when the entity is loaded
257 // the next time, the statistics will be loaded again.
258 $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache([$comment->getCommentedEntityId()]);