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 }
|