annotate core/modules/comment/src/CommentStatistics.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\comment;
Chris@0 4
Chris@0 5 use Drupal\Core\Database\Connection;
Chris@18 6 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
Chris@18 7 use Drupal\Core\Entity\EntityTypeManagerInterface;
Chris@0 8 use Drupal\Core\Entity\FieldableEntityInterface;
Chris@0 9 use Drupal\Core\Entity\EntityChangedInterface;
Chris@0 10 use Drupal\Core\Entity\EntityInterface;
Chris@0 11 use Drupal\Core\State\StateInterface;
Chris@0 12 use Drupal\Core\Session\AccountInterface;
Chris@0 13 use Drupal\user\EntityOwnerInterface;
Chris@0 14
Chris@0 15 class CommentStatistics implements CommentStatisticsInterface {
Chris@18 16 use DeprecatedServicePropertyTrait;
Chris@18 17
Chris@18 18 /**
Chris@18 19 * {@inheritdoc}
Chris@18 20 */
Chris@18 21 protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
Chris@0 22
Chris@0 23 /**
Chris@0 24 * The current database connection.
Chris@0 25 *
Chris@0 26 * @var \Drupal\Core\Database\Connection
Chris@0 27 */
Chris@0 28 protected $database;
Chris@0 29
Chris@0 30 /**
Chris@18 31 * The replica database connection.
Chris@18 32 *
Chris@18 33 * @var \Drupal\Core\Database\Connection
Chris@18 34 */
Chris@18 35 protected $databaseReplica;
Chris@18 36
Chris@18 37 /**
Chris@0 38 * The current logged in user.
Chris@0 39 *
Chris@0 40 * @var \Drupal\Core\Session\AccountInterface
Chris@0 41 */
Chris@0 42 protected $currentUser;
Chris@0 43
Chris@0 44 /**
Chris@18 45 * The entity type manager.
Chris@0 46 *
Chris@18 47 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
Chris@0 48 */
Chris@18 49 protected $entityTypeManager;
Chris@0 50
Chris@0 51 /**
Chris@0 52 * The state service.
Chris@0 53 *
Chris@0 54 * @var \Drupal\Core\State\StateInterface
Chris@0 55 */
Chris@0 56 protected $state;
Chris@0 57
Chris@0 58 /**
Chris@0 59 * Constructs the CommentStatistics service.
Chris@0 60 *
Chris@0 61 * @param \Drupal\Core\Database\Connection $database
Chris@0 62 * The active database connection.
Chris@0 63 * @param \Drupal\Core\Session\AccountInterface $current_user
Chris@0 64 * The current logged in user.
Chris@18 65 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
Chris@18 66 * The entity type manager.
Chris@0 67 * @param \Drupal\Core\State\StateInterface $state
Chris@0 68 * The state service.
Chris@18 69 * @param \Drupal\Core\Database\Connection|null $database_replica
Chris@18 70 * (Optional) the replica database connection.
Chris@0 71 */
Chris@18 72 public function __construct(Connection $database, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, StateInterface $state, Connection $database_replica = NULL) {
Chris@0 73 $this->database = $database;
Chris@18 74 $this->databaseReplica = $database_replica ?: $database;
Chris@0 75 $this->currentUser = $current_user;
Chris@18 76 $this->entityTypeManager = $entity_type_manager;
Chris@0 77 $this->state = $state;
Chris@0 78 }
Chris@0 79
Chris@0 80 /**
Chris@0 81 * {@inheritdoc}
Chris@0 82 */
Chris@0 83 public function read($entities, $entity_type, $accurate = TRUE) {
Chris@18 84 $connection = $accurate ? $this->database : $this->databaseReplica;
Chris@18 85 $stats = $connection->select('comment_entity_statistics', 'ces')
Chris@0 86 ->fields('ces')
Chris@0 87 ->condition('ces.entity_id', array_keys($entities), 'IN')
Chris@0 88 ->condition('ces.entity_type', $entity_type)
Chris@0 89 ->execute();
Chris@0 90
Chris@0 91 $statistics_records = [];
Chris@0 92 while ($entry = $stats->fetchObject()) {
Chris@0 93 $statistics_records[] = $entry;
Chris@0 94 }
Chris@0 95 return $statistics_records;
Chris@0 96 }
Chris@0 97
Chris@0 98 /**
Chris@0 99 * {@inheritdoc}
Chris@0 100 */
Chris@0 101 public function delete(EntityInterface $entity) {
Chris@0 102 $this->database->delete('comment_entity_statistics')
Chris@0 103 ->condition('entity_id', $entity->id())
Chris@0 104 ->condition('entity_type', $entity->getEntityTypeId())
Chris@0 105 ->execute();
Chris@0 106 }
Chris@0 107
Chris@0 108 /**
Chris@0 109 * {@inheritdoc}
Chris@0 110 */
Chris@0 111 public function create(FieldableEntityInterface $entity, $fields) {
Chris@0 112 $query = $this->database->insert('comment_entity_statistics')
Chris@0 113 ->fields([
Chris@0 114 'entity_id',
Chris@0 115 'entity_type',
Chris@0 116 'field_name',
Chris@0 117 'cid',
Chris@0 118 'last_comment_timestamp',
Chris@0 119 'last_comment_name',
Chris@0 120 'last_comment_uid',
Chris@0 121 'comment_count',
Chris@0 122 ]);
Chris@0 123 foreach ($fields as $field_name => $detail) {
Chris@0 124 // Skip fields that entity does not have.
Chris@0 125 if (!$entity->hasField($field_name)) {
Chris@0 126 continue;
Chris@0 127 }
Chris@0 128 // Get the user ID from the entity if it's set, or default to the
Chris@0 129 // currently logged in user.
Chris@0 130 $last_comment_uid = 0;
Chris@0 131 if ($entity instanceof EntityOwnerInterface) {
Chris@0 132 $last_comment_uid = $entity->getOwnerId();
Chris@0 133 }
Chris@0 134 if (!isset($last_comment_uid)) {
Chris@0 135 // Default to current user when entity does not implement
Chris@0 136 // EntityOwnerInterface or author is not set.
Chris@0 137 $last_comment_uid = $this->currentUser->id();
Chris@0 138 }
Chris@0 139 // Default to REQUEST_TIME when entity does not have a changed property.
Chris@0 140 $last_comment_timestamp = REQUEST_TIME;
Chris@0 141 // @todo Make comment statistics language aware and add some tests. See
Chris@0 142 // https://www.drupal.org/node/2318875
Chris@0 143 if ($entity instanceof EntityChangedInterface) {
Chris@0 144 $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
Chris@0 145 }
Chris@0 146 $query->values([
Chris@0 147 'entity_id' => $entity->id(),
Chris@0 148 'entity_type' => $entity->getEntityTypeId(),
Chris@0 149 'field_name' => $field_name,
Chris@0 150 'cid' => 0,
Chris@0 151 'last_comment_timestamp' => $last_comment_timestamp,
Chris@0 152 'last_comment_name' => NULL,
Chris@0 153 'last_comment_uid' => $last_comment_uid,
Chris@0 154 'comment_count' => 0,
Chris@0 155 ]);
Chris@0 156 }
Chris@0 157 $query->execute();
Chris@0 158 }
Chris@0 159
Chris@0 160 /**
Chris@0 161 * {@inheritdoc}
Chris@0 162 */
Chris@0 163 public function getMaximumCount($entity_type) {
Chris@0 164 return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', [':entity_type' => $entity_type])->fetchField();
Chris@0 165 }
Chris@0 166
Chris@0 167 /**
Chris@0 168 * {@inheritdoc}
Chris@0 169 */
Chris@0 170 public function getRankingInfo() {
Chris@0 171 return [
Chris@0 172 'comments' => [
Chris@0 173 'title' => t('Number of comments'),
Chris@0 174 'join' => [
Chris@0 175 'type' => 'LEFT',
Chris@0 176 'table' => 'comment_entity_statistics',
Chris@0 177 'alias' => 'ces',
Chris@0 178 // Default to comment field as this is the most common use case for
Chris@0 179 // nodes.
Chris@0 180 'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_name = 'comment'",
Chris@0 181 ],
Chris@0 182 // Inverse law that maps the highest view count on the site to 1 and 0
Chris@0 183 // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
Chris@0 184 // in order to ensure that the :comment_scale argument is treated as
Chris@0 185 // a numeric type, because the PostgreSQL PDO driver sometimes puts
Chris@0 186 // values in as strings instead of numbers in complex expressions like
Chris@0 187 // this.
Chris@0 188 'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * (ROUND(:comment_scale, 4)))',
Chris@0 189 'arguments' => [':comment_scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0],
Chris@0 190 ],
Chris@0 191 ];
Chris@0 192 }
Chris@0 193
Chris@0 194 /**
Chris@0 195 * {@inheritdoc}
Chris@0 196 */
Chris@0 197 public function update(CommentInterface $comment) {
Chris@0 198 // Allow bulk updates and inserts to temporarily disable the maintenance of
Chris@0 199 // the {comment_entity_statistics} table.
Chris@0 200 if (!$this->state->get('comment.maintain_entity_statistics')) {
Chris@0 201 return;
Chris@0 202 }
Chris@0 203
Chris@0 204 $query = $this->database->select('comment_field_data', 'c');
Chris@0 205 $query->addExpression('COUNT(cid)');
Chris@0 206 $count = $query->condition('c.entity_id', $comment->getCommentedEntityId())
Chris@0 207 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
Chris@0 208 ->condition('c.field_name', $comment->getFieldName())
Chris@0 209 ->condition('c.status', CommentInterface::PUBLISHED)
Chris@0 210 ->condition('default_langcode', 1)
Chris@0 211 ->execute()
Chris@0 212 ->fetchField();
Chris@0 213
Chris@0 214 if ($count > 0) {
Chris@0 215 // Comments exist.
Chris@0 216 $last_reply = $this->database->select('comment_field_data', 'c')
Chris@0 217 ->fields('c', ['cid', 'name', 'changed', 'uid'])
Chris@0 218 ->condition('c.entity_id', $comment->getCommentedEntityId())
Chris@0 219 ->condition('c.entity_type', $comment->getCommentedEntityTypeId())
Chris@0 220 ->condition('c.field_name', $comment->getFieldName())
Chris@0 221 ->condition('c.status', CommentInterface::PUBLISHED)
Chris@0 222 ->condition('default_langcode', 1)
Chris@0 223 ->orderBy('c.created', 'DESC')
Chris@0 224 ->range(0, 1)
Chris@0 225 ->execute()
Chris@0 226 ->fetchObject();
Chris@0 227 // Use merge here because entity could be created before comment field.
Chris@0 228 $this->database->merge('comment_entity_statistics')
Chris@0 229 ->fields([
Chris@0 230 'cid' => $last_reply->cid,
Chris@0 231 'comment_count' => $count,
Chris@0 232 'last_comment_timestamp' => $last_reply->changed,
Chris@0 233 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
Chris@0 234 'last_comment_uid' => $last_reply->uid,
Chris@0 235 ])
Chris@0 236 ->keys([
Chris@0 237 'entity_id' => $comment->getCommentedEntityId(),
Chris@0 238 'entity_type' => $comment->getCommentedEntityTypeId(),
Chris@0 239 'field_name' => $comment->getFieldName(),
Chris@0 240 ])
Chris@0 241 ->execute();
Chris@0 242 }
Chris@0 243 else {
Chris@0 244 // Comments do not exist.
Chris@0 245 $entity = $comment->getCommentedEntity();
Chris@0 246 // Get the user ID from the entity if it's set, or default to the
Chris@0 247 // currently logged in user.
Chris@0 248 if ($entity instanceof EntityOwnerInterface) {
Chris@0 249 $last_comment_uid = $entity->getOwnerId();
Chris@0 250 }
Chris@0 251 if (!isset($last_comment_uid)) {
Chris@0 252 // Default to current user when entity does not implement
Chris@0 253 // EntityOwnerInterface or author is not set.
Chris@0 254 $last_comment_uid = $this->currentUser->id();
Chris@0 255 }
Chris@0 256 $this->database->update('comment_entity_statistics')
Chris@0 257 ->fields([
Chris@0 258 'cid' => 0,
Chris@0 259 'comment_count' => 0,
Chris@0 260 // Use the changed date of the entity if it's set, or default to
Chris@0 261 // REQUEST_TIME.
Chris@0 262 'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
Chris@0 263 'last_comment_name' => '',
Chris@0 264 'last_comment_uid' => $last_comment_uid,
Chris@0 265 ])
Chris@0 266 ->condition('entity_id', $comment->getCommentedEntityId())
Chris@0 267 ->condition('entity_type', $comment->getCommentedEntityTypeId())
Chris@0 268 ->condition('field_name', $comment->getFieldName())
Chris@0 269 ->execute();
Chris@0 270 }
Chris@0 271
Chris@0 272 // Reset the cache of the commented entity so that when the entity is loaded
Chris@0 273 // the next time, the statistics will be loaded again.
Chris@18 274 $this->entityTypeManager->getStorage($comment->getCommentedEntityTypeId())->resetCache([$comment->getCommentedEntityId()]);
Chris@0 275 }
Chris@0 276
Chris@0 277 }