Comment.php 14.2 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Definition of Drupal\comment\Entity\Comment.
6
7
 */

8
namespace Drupal\comment\Entity;
9

10
use Drupal\Component\Utility\Number;
11
use Drupal\Core\Entity\ContentEntityBase;
12
use Drupal\comment\CommentInterface;
13
use Drupal\Core\Entity\EntityStorageControllerInterface;
14
use Drupal\Core\Field\FieldDefinition;
15
use Drupal\Core\Language\Language;
16
use Drupal\Core\TypedData\DataDefinition;
17
18
19

/**
 * Defines the comment entity class.
20
 *
21
 * @EntityType(
22
23
 *   id = "comment",
 *   label = @Translation("Comment"),
24
 *   bundle_label = @Translation("Content type"),
25
26
27
 *   controllers = {
 *     "storage" = "Drupal\comment\CommentStorageController",
 *     "access" = "Drupal\comment\CommentAccessController",
28
 *     "view_builder" = "Drupal\comment\CommentViewBuilder",
29
 *     "form" = {
30
31
 *       "default" = "Drupal\comment\CommentFormController",
 *       "delete" = "Drupal\comment\Form\DeleteForm"
32
33
 *     },
 *     "translation" = "Drupal\comment\CommentTranslationController"
34
35
36
37
 *   },
 *   base_table = "comment",
 *   uri_callback = "comment_uri",
 *   fieldable = TRUE,
38
 *   translatable = TRUE,
39
 *   render_cache = FALSE,
40
41
 *   entity_keys = {
 *     "id" = "cid",
42
 *     "bundle" = "field_id",
43
44
 *     "label" = "subject",
 *     "uuid" = "uuid"
45
 *   },
46
47
48
 *   bundle_keys = {
 *     "bundle" = "field_id"
 *   },
49
 *   links = {
50
 *     "canonical" = "comment.permalink",
51
52
 *     "edit-form" = "comment.edit_page",
 *     "admin-form" = "comment.bundle"
53
54
 *   }
 * )
55
 */
56
class Comment extends ContentEntityBase implements CommentInterface {
57

58
59
60
61
62
  /**
   * The thread for which a lock was acquired.
   */
  protected $threadLock = '';

63
64
65
  /**
   * The comment ID.
   *
66
   * @var \Drupal\Core\Field\FieldItemListInterface
67
68
69
   */
  public $cid;

70
71
72
  /**
   * The comment UUID.
   *
73
   * @var \Drupal\Core\Field\FieldItemListInterface
74
75
76
   */
  public $uuid;

77
  /**
78
   * The parent comment ID if this is a reply to another comment.
79
   *
80
   * @var \Drupal\Core\Field\FieldItemListInterface
81
82
83
   */
  public $pid;

84
  /**
85
86
   * The entity ID for the entity to which this comment is attached.
   *
87
   * @var \Drupal\Core\Field\FieldItemListInterface
88
89
90
91
92
   */
  public $entity_id;

  /**
   * The entity type of the entity to which this comment is attached.
93
   *
94
   * @var \Drupal\Core\Field\FieldItemListInterface
95
   */
96
97
98
99
100
  public $entity_type;

  /**
   * The field to which this comment is attached.
   *
101
   * @var \Drupal\Core\Field\FieldItemListInterface
102
103
   */
  public $field_id;
104

105
106
107
  /**
   * The comment language code.
   *
108
   * @var \Drupal\Core\Field\FieldItemListInterface
109
   */
110
  public $langcode;
111
112
113
114

  /**
   * The comment title.
   *
115
   * @var \Drupal\Core\Field\FieldItemListInterface
116
117
118
119
120
121
   */
  public $subject;

  /**
   * The comment author ID.
   *
122
   * @var \Drupal\Core\Field\FieldItemListInterface
123
   */
124
  public $uid;
125
126
127
128
129
130

  /**
   * The comment author's name.
   *
   * For anonymous authors, this is the value as typed in the comment form.
   *
131
   * @var \Drupal\Core\Field\FieldItemListInterface
132
   */
133
  public $name;
134
135
136
137
138
139

  /**
   * The comment author's e-mail address.
   *
   * For anonymous authors, this is the value as typed in the comment form.
   *
140
   * @var \Drupal\Core\Field\FieldItemListInterface
141
142
143
144
145
146
147
148
   */
  public $mail;

  /**
   * The comment author's home page address.
   *
   * For anonymous authors, this is the value as typed in the comment form.
   *
149
   * @var \Drupal\Core\Field\FieldItemListInterface
150
151
152
153
   */
  public $homepage;

  /**
154
155
   * The comment author's hostname.
   *
156
   * @var \Drupal\Core\Field\FieldItemListInterface
157
   */
158
159
160
161
162
  public $hostname;

  /**
   * The time that the comment was created.
   *
163
   * @var \Drupal\Core\Field\FieldItemListInterface
164
165
166
167
168
169
   */
  public $created;

  /**
   * The time that the comment was last edited.
   *
170
   * @var \Drupal\Core\Field\FieldItemListInterface
171
172
173
174
175
176
   */
  public $changed;

  /**
   * A boolean field indicating whether the comment is published.
   *
177
   * @var \Drupal\Core\Field\FieldItemListInterface
178
179
180
181
182
183
   */
  public $status;

  /**
   * The alphadecimal representation of the comment's place in a thread.
   *
184
   * @var \Drupal\Core\Field\FieldItemListInterface
185
186
187
188
189
190
191
192
193
194
195
196
   */
  public $thread;

  /**
   * Initialize the object. Invoked upon construction and wake up.
   */
  protected function init() {
    parent::init();
    // We unset all defined properties, so magic getters apply.
    unset($this->cid);
    unset($this->uuid);
    unset($this->pid);
197
198
    unset($this->entity_id);
    unset($this->field_id);
199
200
201
202
203
204
205
206
207
208
    unset($this->subject);
    unset($this->uid);
    unset($this->name);
    unset($this->mail);
    unset($this->homepage);
    unset($this->hostname);
    unset($this->created);
    unset($this->changed);
    unset($this->status);
    unset($this->thread);
209
    unset($this->entity_type);
210
211
212
  }

  /**
213
   * Implements Drupal\Core\Entity\EntityInterface::id().
214
   */
215
216
  public function id() {
    return $this->get('cid')->value;
217
  }
218

219
220
221
222
  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageControllerInterface $storage_controller) {
223
224
    parent::preSave($storage_controller);

225
    if (!isset($this->status->value)) {
226
      $this->status->value = \Drupal::currentUser()->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED;
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    }
    if ($this->isNew()) {
      // Add the comment to database. This next section builds the thread field.
      // Also see the documentation for comment_view().
      if (!empty($this->thread->value)) {
        // Allow calling code to set thread itself.
        $thread = $this->thread->value;
      }
      else {
        if ($this->threadLock) {
          // As preSave() is protected, this can only happen when this class
          // is extended in a faulty manner.
          throw new \LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
        }
        if ($this->pid->target_id == 0) {
          // This is a comment with no parent comment (depth 0): we start
          // by retrieving the maximum thread level.
          $max = $storage_controller->getMaxThread($this);
          // Strip the "/" from the end of the thread.
          $max = rtrim($max, '/');
          // We need to get the value at the correct depth.
          $parts = explode('.', $max);
249
          $n = Number::alphadecimalToInt($parts[0]);
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
          $prefix = '';
        }
        else {
          // This is a comment with a parent comment, so increase the part of
          // the thread value at the proper depth.

          // Get the parent comment:
          $parent = $this->pid->entity;
          // Strip the "/" from the end of the parent thread.
          $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/');
          $prefix = $parent->thread->value . '.';
          // Get the max value in *this* thread.
          $max = $storage_controller->getMaxThreadPerThread($this);

          if ($max == '') {
            // First child of this parent. As the other two cases do an
            // increment of the thread number before creating the thread
            // string set this to -1 so it requires an increment too.
            $n = -1;
          }
          else {
            // Strip the "/" at the end of the thread.
            $max = rtrim($max, '/');
            // Get the value at the correct depth.
            $parts = explode('.', $max);
            $parent_depth = count(explode('.', $parent->thread->value));
276
            $n = Number::alphadecimalToInt($parts[$parent_depth]);
277
278
279
          }
        }
        // Finally, build the thread field for this new comment. To avoid
280
        // race conditions, get a lock on the thread. If another process already
281
282
        // has the lock, just move to the next integer.
        do {
283
          $thread = $prefix . Number::intToAlphadecimal(++$n) . '/';
284
285
286
          $lock_name = "comment:{$this->entity_id->value}:$thread";
        } while (!\Drupal::lock()->acquire($lock_name));
        $this->threadLock = $lock_name;
287
288
289
290
291
292
293
294
295
      }
      if (empty($this->created->value)) {
        $this->created->value = REQUEST_TIME;
      }
      if (empty($this->changed->value)) {
        $this->changed->value = $this->created->value;
      }
      // We test the value with '===' because we need to modify anonymous
      // users as well.
296
297
      if ($this->uid->target_id === \Drupal::currentUser()->id() && \Drupal::currentUser()->isAuthenticated()) {
        $this->name->value = \Drupal::currentUser()->getUsername();
298
299
300
301
302
303
304
305
306
307
308
      }
      // Add the values which aren't passed into the function.
      $this->thread->value = $thread;
      $this->hostname->value = \Drupal::request()->getClientIP();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
309
310
    parent::postSave($storage_controller, $update);

311
    $this->releaseThreadLock();
312
313
    // Update the {comment_entity_statistics} table prior to executing the hook.
    $storage_controller->updateEntityStatistics($this);
314
    if ($this->status->value == CommentInterface::PUBLISHED) {
315
316
317
318
319
320
321
322
323
      module_invoke_all('comment_publish', $this);
    }
  }

  /**
   * Release the lock acquired for the thread in preSave().
   */
  protected function releaseThreadLock() {
    if ($this->threadLock) {
324
      \Drupal::lock()->release($this->threadLock);
325
326
327
328
329
330
331
332
      $this->threadLock = '';
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
333
334
    parent::postDelete($storage_controller, $entities);

335
336
337
    $child_cids = $storage_controller->getChildCids($entities);
    entity_delete_multiple('comment', $child_cids);

338
339
    foreach ($entities as $id => $entity) {
      $storage_controller->updateEntityStatistics($entity);
340
341
342
    }
  }

343
344
345
346
  /**
   * {@inheritdoc}
   */
  public function permalink() {
347
348
349
    $entity = entity_load($this->get('entity_type')->value, $this->get('entity_id')->value);
    $uri = $entity->uri();
    $url['path'] = $uri['path'];
350
351
352
353
    $url['options'] = array('fragment' => 'comment-' . $this->id());

    return $url;
  }
354
355
356
357
358

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions($entity_type) {
359
360
361
362
363
364
365
366
367
368
369
370
371
    $fields['cid'] = FieldDefinition::create('integer')
      ->setLabel(t('Comment ID'))
      ->setDescription(t('The comment ID.'))
      ->setReadOnly(TRUE);

    $fields['uuid'] = FieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The comment UUID.'))
      ->setReadOnly(TRUE);

    $fields['pid'] = FieldDefinition::create('entity_reference')
      ->setLabel(t('Parent ID'))
      ->setDescription(t('The parent comment ID if this is a reply to a comment.'))
372
      ->setSetting('target_type', 'comment');
373
374
375
376

    $fields['entity_id'] = FieldDefinition::create('entity_reference')
      ->setLabel(t('Entity ID'))
      ->setDescription(t('The ID of the entity of which this comment is a reply.'))
377
      ->setSetting('target_type', 'node')
378
379
380
381
382
383
384
385
386
387
388
389
390
      ->setRequired(TRUE);

    $fields['langcode'] = FieldDefinition::create('language')
      ->setLabel(t('Language code'))
      ->setDescription(t('The comment language code.'));

    $fields['subject'] = FieldDefinition::create('string')
      ->setLabel(t('Subject'))
      ->setDescription(t('The comment title or subject.'));

    $fields['uid'] = FieldDefinition::create('entity_reference')
      ->setLabel(t('User ID'))
      ->setDescription(t('The user ID of the comment author.'))
391
      ->setSettings(array(
392
393
        'target_type' => 'user',
        'default_value' => 0,
394
395
396
397
398
      ));

    $fields['name'] = FieldDefinition::create('string')
      ->setLabel(t('Name'))
      ->setDescription(t("The comment author's name."))
399
      ->setSetting('default_value', '');
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451

    $fields['mail'] = FieldDefinition::create('email')
      ->setLabel(t('Email'))
      ->setDescription(t("The comment author's e-mail address."));

    $fields['homepage'] = FieldDefinition::create('string')
      ->setLabel(t('Homepage'))
      ->setDescription(t("The comment author's home page address."));

    $fields['hostname'] = FieldDefinition::create('string')
      ->setLabel(t('Hostname'))
      ->setDescription(t("The comment author's hostname."));

    // @todo Convert to a "created" field in https://drupal.org/node/2145103.
    $fields['created'] = FieldDefinition::create('integer')
      ->setLabel(t('Created'))
      ->setDescription(t('The time that the comment was created.'));

    // @todo Convert to a "changed" field in https://drupal.org/node/2145103.
    $fields['changed'] = FieldDefinition::create('integer')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the comment was last edited.'))
      ->setPropertyConstraints('value', array('EntityChanged' => array()));

    $fields['status'] = FieldDefinition::create('boolean')
      ->setLabel(t('Publishing status'))
      ->setDescription(t('A boolean indicating whether the comment is published.'));

    $fields['thread'] = FieldDefinition::create('string')
      ->setLabel(t('Thread place'))
      ->setDescription(t("The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length."));

    $fields['entity_type'] = FieldDefinition::create('string')
      ->setLabel(t('Entity type'))
      ->setDescription(t('The entity type to which this comment is attached.'));

    // @todo Convert to aa entity_reference field in
    // https://drupal.org/node/2149859.
    $fields['field_id'] = FieldDefinition::create('string')
      ->setLabel(t('Field ID'))
      ->setDescription(t('The comment field id.'));

    $fields['field_name'] = FieldDefinition::create('string')
      ->setLabel(t('Comment field name'))
      ->setDescription(t('The field name through which this comment was added.'))
      ->setComputed(TRUE);

    $item_definition = $fields['field_name']->getItemDefinition();
    $item_definition->setClass('\Drupal\comment\CommentFieldName');
    $fields['field_name']->setItemDefinition($item_definition);

    return $fields;
452
  }
453
454
455
456
457
458
459
460

  /**
   * {@inheritdoc}
   */
  public function getChangedTime() {
    return $this->changed->value;
  }

461
462
463
464
465
466
467
468
469
  /**
   * {@inheritdoc}
   */
  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
    if (empty($values['field_id']) && !empty($values['field_name']) && !empty($values['entity_type'])) {
      $values['field_id'] = $values['entity_type'] . '__' . $values['field_name'];
    }
  }

470
}