d164d8ad9e9987a3ef286cb9ac1dab1804147007
[yaffs-website] / web / core / modules / comment / src / CommentStorage.php
1 <?php
2
3 namespace Drupal\comment;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Database\Connection;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\FieldableEntityInterface;
11 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
12 use Drupal\Core\Session\AccountInterface;
13 use Drupal\Core\Language\LanguageManagerInterface;
14 use Symfony\Component\DependencyInjection\ContainerInterface;
15
16 /**
17  * Defines the storage handler class for comments.
18  *
19  * This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class,
20  * adding required special handling for comment entities.
21  */
22 class CommentStorage extends SqlContentEntityStorage implements CommentStorageInterface {
23
24   /**
25    * The current user.
26    *
27    * @var \Drupal\Core\Session\AccountInterface
28    */
29   protected $currentUser;
30
31   /**
32    * Constructs a CommentStorage object.
33    *
34    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
35    *   An array of entity info for the entity type.
36    * @param \Drupal\Core\Database\Connection $database
37    *   The database connection to be used.
38    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
39    *   The entity manager.
40    * @param \Drupal\Core\Session\AccountInterface $current_user
41    *   The current user.
42    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
43    *   Cache backend instance to use.
44    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
45    *   The language manager.
46    */
47   public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
48     parent::__construct($entity_info, $database, $entity_manager, $cache, $language_manager);
49     $this->currentUser = $current_user;
50   }
51
52   /**
53    * {@inheritdoc}
54    */
55   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
56     return new static(
57       $entity_info,
58       $container->get('database'),
59       $container->get('entity.manager'),
60       $container->get('current_user'),
61       $container->get('cache.entity'),
62       $container->get('language_manager')
63     );
64   }
65
66   /**
67    * {@inheritdoc}
68    */
69   public function getMaxThread(CommentInterface $comment) {
70     $query = $this->database->select('comment_field_data', 'c')
71       ->condition('entity_id', $comment->getCommentedEntityId())
72       ->condition('field_name', $comment->getFieldName())
73       ->condition('entity_type', $comment->getCommentedEntityTypeId())
74       ->condition('default_langcode', 1);
75     $query->addExpression('MAX(thread)', 'thread');
76     return $query->execute()
77       ->fetchField();
78   }
79
80   /**
81    * {@inheritdoc}
82    */
83   public function getMaxThreadPerThread(CommentInterface $comment) {
84     $query = $this->database->select('comment_field_data', 'c')
85       ->condition('entity_id', $comment->getCommentedEntityId())
86       ->condition('field_name', $comment->getFieldName())
87       ->condition('entity_type', $comment->getCommentedEntityTypeId())
88       ->condition('thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
89       ->condition('default_langcode', 1);
90     $query->addExpression('MAX(thread)', 'thread');
91     return $query->execute()
92       ->fetchField();
93   }
94
95   /**
96    * {@inheritdoc}
97    */
98   public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $divisor = 1) {
99     // Count how many comments (c1) are before $comment (c2) in display order.
100     // This is the 0-based display ordinal.
101     $query = $this->database->select('comment_field_data', 'c1');
102     $query->innerJoin('comment_field_data', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_name = c1.field_name');
103     $query->addExpression('COUNT(*)', 'count');
104     $query->condition('c2.cid', $comment->id());
105     if (!$this->currentUser->hasPermission('administer comments')) {
106       $query->condition('c1.status', CommentInterface::PUBLISHED);
107     }
108
109     if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
110       // For rendering flat comments, cid is used for ordering comments due to
111       // unpredictable behavior with timestamp, so we make the same assumption
112       // here.
113       $query->condition('c1.cid', $comment->id(), '<');
114     }
115     else {
116       // For threaded comments, the c.thread column is used for ordering. We can
117       // use the sorting code for comparison, but must remove the trailing
118       // slash.
119       $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) - 1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) - 1))');
120     }
121
122     $query->condition('c1.default_langcode', 1);
123     $query->condition('c2.default_langcode', 1);
124
125     $ordinal = $query->execute()->fetchField();
126
127     return ($divisor > 1) ? floor($ordinal / $divisor) : $ordinal;
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name) {
134     $field = $entity->getFieldDefinition($field_name);
135     $comments_per_page = $field->getSetting('per_page');
136
137     if ($total_comments <= $comments_per_page) {
138       // Only one page of comments.
139       $count = 0;
140     }
141     elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
142       // Flat comments.
143       $count = $total_comments - $new_comments;
144     }
145     else {
146       // Threaded comments.
147
148       // 1. Find all the threads with a new comment.
149       $unread_threads_query = $this->database->select('comment_field_data', 'comment')
150         ->fields('comment', ['thread'])
151         ->condition('entity_id', $entity->id())
152         ->condition('entity_type', $entity->getEntityTypeId())
153         ->condition('field_name', $field_name)
154         ->condition('status', CommentInterface::PUBLISHED)
155         ->condition('default_langcode', 1)
156         ->orderBy('created', 'DESC')
157         ->orderBy('cid', 'DESC')
158         ->range(0, $new_comments);
159
160       // 2. Find the first thread.
161       $first_thread_query = $this->database->select($unread_threads_query, 'thread');
162       $first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder');
163       $first_thread = $first_thread_query
164         ->fields('thread', ['thread'])
165         ->orderBy('torder')
166         ->range(0, 1)
167         ->execute()
168         ->fetchField();
169
170       // Remove the final '/'.
171       $first_thread = substr($first_thread, 0, -1);
172
173       // Find the number of the first comment of the first unread thread.
174       $count = $this->database->query('SELECT COUNT(*) FROM {comment_field_data} WHERE entity_id = :entity_id
175                         AND entity_type = :entity_type
176                         AND field_name = :field_name
177                         AND status = :status
178                         AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread
179                         AND default_langcode = 1', [
180         ':status' => CommentInterface::PUBLISHED,
181         ':entity_id' => $entity->id(),
182         ':field_name' => $field_name,
183         ':entity_type' => $entity->getEntityTypeId(),
184         ':thread' => $first_thread,
185       ])->fetchField();
186     }
187
188     return $comments_per_page > 0 ? (int) ($count / $comments_per_page) : 0;
189   }
190
191   /**
192    * {@inheritdoc}
193    */
194   public function getChildCids(array $comments) {
195     return $this->database->select('comment_field_data', 'c')
196       ->fields('c', ['cid'])
197       ->condition('pid', array_keys($comments), 'IN')
198       ->condition('default_langcode', 1)
199       ->execute()
200       ->fetchCol();
201   }
202
203   /**
204    * {@inheritdoc}
205    *
206    * To display threaded comments in the correct order we keep a 'thread' field
207    * and order by that value. This field keeps this data in
208    * a way which is easy to update and convenient to use.
209    *
210    * A "thread" value starts at "1". If we add a child (A) to this comment,
211    * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
212    * brother of (A) will get "1.2". Next brother of the parent of (A) will get
213    * "2" and so on.
214    *
215    * First of all note that the thread field stores the depth of the comment:
216    * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
217    *
218    * Now to get the ordering right, consider this example:
219    *
220    * 1
221    * 1.1
222    * 1.1.1
223    * 1.2
224    * 2
225    *
226    * If we "ORDER BY thread ASC" we get the above result, and this is the
227    * natural order sorted by time. However, if we "ORDER BY thread DESC"
228    * we get:
229    *
230    * 2
231    * 1.2
232    * 1.1.1
233    * 1.1
234    * 1
235    *
236    * Clearly, this is not a natural way to see a thread, and users will get
237    * confused. The natural order to show a thread by time desc would be:
238    *
239    * 2
240    * 1
241    * 1.2
242    * 1.1
243    * 1.1.1
244    *
245    * which is what we already did before the standard pager patch. To achieve
246    * this we simply add a "/" at the end of each "thread" value. This way, the
247    * thread fields will look like this:
248    *
249    * 1/
250    * 1.1/
251    * 1.1.1/
252    * 1.2/
253    * 2/
254    *
255    * we add "/" since this char is, in ASCII, higher than every number, so if
256    * now we "ORDER BY thread DESC" we get the correct order. However this would
257    * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
258    * to consider the trailing "/" so we use a substring only.
259    */
260   public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page = 0, $pager_id = 0) {
261     $query = $this->database->select('comment_field_data', 'c');
262     $query->addField('c', 'cid');
263     $query
264       ->condition('c.entity_id', $entity->id())
265       ->condition('c.entity_type', $entity->getEntityTypeId())
266       ->condition('c.field_name', $field_name)
267       ->condition('c.default_langcode', 1)
268       ->addTag('entity_access')
269       ->addTag('comment_filter')
270       ->addMetaData('base_table', 'comment')
271       ->addMetaData('entity', $entity)
272       ->addMetaData('field_name', $field_name);
273
274     if ($comments_per_page) {
275       $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')
276         ->limit($comments_per_page);
277       if ($pager_id) {
278         $query->element($pager_id);
279       }
280
281       $count_query = $this->database->select('comment_field_data', 'c');
282       $count_query->addExpression('COUNT(*)');
283       $count_query
284         ->condition('c.entity_id', $entity->id())
285         ->condition('c.entity_type', $entity->getEntityTypeId())
286         ->condition('c.field_name', $field_name)
287         ->condition('c.default_langcode', 1)
288         ->addTag('entity_access')
289         ->addTag('comment_filter')
290         ->addMetaData('base_table', 'comment')
291         ->addMetaData('entity', $entity)
292         ->addMetaData('field_name', $field_name);
293       $query->setCountQuery($count_query);
294     }
295
296     if (!$this->currentUser->hasPermission('administer comments')) {
297       $query->condition('c.status', CommentInterface::PUBLISHED);
298       if ($comments_per_page) {
299         $count_query->condition('c.status', CommentInterface::PUBLISHED);
300       }
301     }
302     if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
303       $query->orderBy('c.cid', 'ASC');
304     }
305     else {
306       // See comment above. Analysis reveals that this doesn't cost too
307       // much. It scales much much better than having the whole comment
308       // structure.
309       $query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
310       $query->orderBy('torder', 'ASC');
311     }
312
313     $cids = $query->execute()->fetchCol();
314
315     $comments = [];
316     if ($cids) {
317       $comments = $this->loadMultiple($cids);
318     }
319
320     return $comments;
321   }
322
323   /**
324    * {@inheritdoc}
325    */
326   public function getUnapprovedCount() {
327     return  $this->database->select('comment_field_data', 'c')
328       ->condition('status', CommentInterface::NOT_PUBLISHED, '=')
329       ->condition('default_langcode', 1)
330       ->countQuery()
331       ->execute()
332       ->fetchField();
333   }
334
335 }