CommentForm.php 15.7 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Definition of Drupal\comment\CommentForm.
6
7
8
9
 */

namespace Drupal\comment;

10
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
11
12
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Unicode;
13
use Drupal\Core\Datetime\DrupalDateTime;
14
use Drupal\Core\Entity\ContentEntityForm;
15
use Drupal\Core\Entity\EntityManagerInterface;
16
use Drupal\Core\Form\FormStateInterface;
17
use Drupal\Core\Language\Language;
18
use Drupal\Core\Language\LanguageInterface;
19
20
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
21
22
23
24

/**
 * Base for controller for comment forms.
 */
25
class CommentForm extends ContentEntityForm {
26

27
28
29
30
31
32
33
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

34
35
36
37
38
39
40
41
42
43
44
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity.manager'),
      $container->get('current_user')
    );
  }

  /**
45
   * Constructs a new CommentForm.
46
   *
47
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
48
49
50
51
   *   The entity manager service.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
52
  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user) {
53
    parent::__construct($entity_manager);
54
55
56
    $this->currentUser = $current_user;
  }

57
58
59
  /**
   * {@inheritdoc}
   */
60
  protected function init(FormStateInterface $form_state) {
61
62
63
64
65
    $comment = $this->entity;

    // Make the comment inherit the current content language unless specifically
    // set.
    if ($comment->isNew()) {
66
      $language_content = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
67
68
69
70
71
72
      $comment->langcode->value = $language_content->id;
    }

    parent::init($form_state);
  }

73
  /**
74
   * Overrides Drupal\Core\Entity\EntityForm::form().
75
   */
76
  public function form(array $form, FormStateInterface $form_state) {
77
    /** @var \Drupal\comment\CommentInterface $comment */
78
    $comment = $this->entity;
79
    $entity = $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->load($comment->getCommentedEntityId());
80
    $field_name = $comment->getFieldName();
81
    $field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
82

83
    // Use #comment-form as unique jump target, regardless of entity type.
84
    $form['#id'] = drupal_html_id('comment_form');
85
    $form['#theme'] = array('comment_form__' . $entity->getEntityTypeId() . '__' . $entity->bundle() . '__' . $field_name, 'comment_form');
86

87
    $anonymous_contact = $field_definition->getSetting('anonymous');
88
    $is_admin = $comment->id() && $this->currentUser->hasPermission('administer comments');
89

90
    if (!$this->currentUser->isAuthenticated() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
91
92
      $form['#attached']['library'][] = 'core/drupal.form';
      $form['#attributes']['data-user-info-from-browser'] = TRUE;
93
94
95
    }

    // If not replying to a comment, use our dedicated page callback for new
96
    // Comments on entities.
97
    if (!$comment->id() && !$comment->hasParentComment()) {
98
      $form['#action'] = url('comment/reply/' . $entity->getEntityTypeId() . '/' . $entity->id() . '/' . $field_name);
99
100
101
102
103
104
    }

    if (isset($form_state['comment_preview'])) {
      $form += $form_state['comment_preview'];
    }

105
    $form['author'] = array();
106
    // Display author information in a details element for comment moderators.
107
108
    if ($is_admin) {
      $form['author'] += array(
109
        '#type' => 'details',
110
        '#title' => $this->t('Administration'),
111
112
113
114
115
      );
    }

    // Prepare default values for form elements.
    if ($is_admin) {
116
      $author = $comment->getAuthorName();
117
      $status = $comment->getStatus();
118
      if (empty($form_state['comment_preview'])) {
119
        $form['#title'] = $this->t('Edit comment %title', array(
120
          '%title' => $comment->getSubject(),
121
122
        ));
      }
123
124
    }
    else {
125
126
      if ($this->currentUser->isAuthenticated()) {
        $author = $this->currentUser->getUsername();
127
128
      }
      else {
129
        $author = ($comment->getAuthorName() ? $comment->getAuthorName() : '');
130
      }
131
      $status = ($this->currentUser->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED);
132
133
134
135
    }

    $date = '';
    if ($comment->id()) {
136
      $date = !empty($comment->date) ? $comment->date : DrupalDateTime::createFromTimestamp($comment->getCreatedTime());
137
138
139
    }

    // Add the author name field depending on the current user.
140
141
    $form['author']['name'] = array(
      '#type' => 'textfield',
142
      '#title' => $this->t('Your name'),
143
      '#default_value' => $author,
144
      '#required' => ($this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
145
146
147
      '#maxlength' => 60,
      '#size' => 30,
    );
148
    if ($is_admin) {
149
150
      $form['author']['name']['#title'] = $this->t('Authored by');
      $form['author']['name']['#description'] = $this->t('Leave blank for %anonymous.', array('%anonymous' => $this->config('user.settings')->get('anonymous')));
151
      $form['author']['name']['#autocomplete_route_name'] = 'user.autocomplete';
152
    }
153
    elseif ($this->currentUser->isAuthenticated()) {
154
155
      $form['author']['name']['#type'] = 'item';
      $form['author']['name']['#value'] = $form['author']['name']['#default_value'];
156
157
      $form['author']['name']['#theme'] = 'username';
      $form['author']['name']['#account'] = $this->currentUser;
158
    }
159
160
161
    elseif($this->currentUser->isAnonymous()) {
      $form['author']['name']['#attributes']['data-drupal-default-value'] = $this->config('user.settings')->get('anonymous');
    }
162

163
164
165
166
167
168
169
170
171
    $language_configuration = \Drupal::moduleHandler()->invoke('language', 'get_default_configuration', array('comment', $comment->getTypeId()));
    $form['langcode'] = array(
      '#title' => t('Language'),
      '#type' => 'language_select',
      '#default_value' => $comment->getUntranslated()->language()->id,
      '#languages' => Language::STATE_ALL,
      '#access' => isset($language_configuration['language_show']) && $language_configuration['language_show'],
    );

172
    // Add author email and homepage fields depending on the current user.
173
174
    $form['author']['mail'] = array(
      '#type' => 'email',
175
      '#title' => $this->t('Email'),
176
      '#default_value' => $comment->getAuthorEmail(),
177
      '#required' => ($this->currentUser->isAnonymous() && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
178
179
      '#maxlength' => 64,
      '#size' => 30,
180
181
      '#description' => $this->t('The content of this field is kept private and will not be shown publicly.'),
      '#access' => $is_admin || ($this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
182
183
184
185
    );

    $form['author']['homepage'] = array(
      '#type' => 'url',
186
      '#title' => $this->t('Homepage'),
187
      '#default_value' => $comment->getHomepage(),
188
189
      '#maxlength' => 255,
      '#size' => 30,
190
      '#access' => $is_admin || ($this->currentUser->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
191
192
193
194
    );

    // Add administrative comment publishing options.
    $form['author']['date'] = array(
195
      '#type' => 'datetime',
196
      '#title' => $this->t('Authored on'),
197
198
199
200
201
202
203
      '#default_value' => $date,
      '#size' => 20,
      '#access' => $is_admin,
    );

    $form['author']['status'] = array(
      '#type' => 'radios',
204
      '#title' => $this->t('Status'),
205
206
      '#default_value' => $status,
      '#options' => array(
207
208
        CommentInterface::PUBLISHED => $this->t('Published'),
        CommentInterface::NOT_PUBLISHED => $this->t('Not published'),
209
210
211
212
213
214
215
      ),
      '#access' => $is_admin,
    );

    // Used for conditional validation of author fields.
    $form['is_anonymous'] = array(
      '#type' => 'value',
216
      '#value' => ($comment->id() ? !$comment->getOwnerId() : $this->currentUser->isAnonymous()),
217
218
219
220
221
222
    );

    return parent::form($form, $form_state, $comment);
  }

  /**
223
   * Overrides Drupal\Core\Entity\EntityForm::actions().
224
   */
225
  protected function actions(array $form, FormStateInterface $form_state) {
226
    $element = parent::actions($form, $form_state);
227
    /* @var \Drupal\comment\CommentInterface $comment */
228
    $comment = $this->entity;
229
    $entity = $comment->getCommentedEntity();
230
231
    $field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
    $preview_mode = $field_definition->getSetting('preview');
232
233
234
235

    // No delete action on the comment form.
    unset($element['delete']);

236
237
238
    // Mark the submit action as the primary action, when it appears.
    $element['submit']['#button_type'] = 'primary';

239
240
    // Only show the save button if comment previews are optional or if we are
    // already previewing the submission.
241
    $element['submit']['#access'] = ($comment->id() && $this->currentUser->hasPermission('administer comments')) || $preview_mode != DRUPAL_REQUIRED || isset($form_state['comment_preview']);
242
243
244

    $element['preview'] = array(
      '#type' => 'submit',
245
      '#value' => $this->t('Preview'),
246
247
248
249
250
251
252
253
254
255
256
257
258
259
      '#access' => $preview_mode != DRUPAL_DISABLED,
      '#validate' => array(
        array($this, 'validate'),
      ),
      '#submit' => array(
        array($this, 'submit'),
        array($this, 'preview'),
      ),
    );

    return $element;
  }

  /**
260
   * Overrides Drupal\Core\Entity\EntityForm::validate().
261
   */
262
  public function validate(array $form, FormStateInterface $form_state) {
263
    parent::validate($form, $form_state);
264
    $entity = $this->entity;
265

266
    if (!$entity->isNew()) {
267
      // Verify the name in case it is being changed from being anonymous.
268
      $accounts = $this->entityManager->getStorage('user')->loadByProperties(array('name' => $form_state->getValue('name')));
269
      $account = reset($accounts);
270
      $form_state->setValue('uid', $account ? $account->id() : 0);
271

272
      $date = $form_state->getValue('date');
273
      if ($date instanceOf DrupalDateTime && $date->hasErrors()) {
274
        $form_state->setErrorByName('date', $this->t('You have to specify a valid date.'));
275
      }
276
      if ($form_state->getValue('name') && !$form_state->getValue('is_anonymous') && !$account) {
277
        $form_state->setErrorByName('name', $this->t('You have to specify a valid author.'));
278
279
      }
    }
280
    elseif ($form_state->getValue('is_anonymous')) {
281
282
283
      // Validate anonymous comment author fields (if given). If the (original)
      // author of this comment was an anonymous user, verify that no registered
      // user with this name exists.
284
285
      if ($form_state->getValue('name')) {
        $accounts = $this->entityManager->getStorage('user')->loadByProperties(array('name' => $form_state->getValue('name')));
286
        if (!empty($accounts)) {
287
          $form_state->setErrorByName('name', $this->t('The name you used belongs to a registered user.'));
288
289
290
291
292
        }
      }
    }
  }

293
  /**
294
   * Overrides EntityForm::buildEntity().
295
   */
296
  public function buildEntity(array $form, FormStateInterface $form_state) {
297
    $comment = parent::buildEntity($form, $form_state);
298
299
    if (!$form_state->isValueEmpty('date') && $form_state->getValue('date') instanceOf DrupalDateTime) {
      $comment->setCreatedTime($form_state->getValue('date')->getTimestamp());
300
301
    }
    else {
302
      $comment->setCreatedTime(REQUEST_TIME);
303
    }
304
305
306
307
    $comment->changed->value = REQUEST_TIME;
    return $comment;
  }

308
  /**
309
   * Overrides Drupal\Core\Entity\EntityForm::submit().
310
   */
311
  public function submit(array $form, FormStateInterface $form_state) {
312
    /** @var \Drupal\comment\CommentInterface $comment */
313
314
315
316
317
    $comment = parent::submit($form, $form_state);

    // If the comment was posted by a registered user, assign the author's ID.
    // @todo Too fragile. Should be prepared and stored in comment_form()
    // already.
318
319
    $author_name = $comment->getAuthorName();
    if (!$comment->is_anonymous && !empty($author_name) && ($account = user_load_by_name($author_name))) {
320
      $comment->setOwner($account);
321
322
323
    }
    // If the comment was posted by an anonymous user and no author name was
    // required, use "Anonymous" by default.
324
325
    if ($comment->is_anonymous && (!isset($author_name) || $author_name === '')) {
      $comment->setAuthorName($this->config('user.settings')->get('anonymous'));
326
327
328
329
    }

    // Validate the comment's subject. If not specified, extract from comment
    // body.
330
    if (trim($comment->getSubject()) == '') {
331
332
333
334
      // The body may be in any format, so:
      // 1) Filter it into HTML
      // 2) Strip out all HTML tags
      // 3) Convert entities back to plain-text.
335
      $comment_text = $comment->comment_body->processed;
336
      $comment->setSubject(Unicode::truncate(trim(String::decodeEntities(strip_tags($comment_text))), 29, TRUE));
337
338
      // Edge cases where the comment body is populated only by HTML tags will
      // require a default subject.
339
340
      if ($comment->getSubject() == '') {
        $comment->setSubject($this->t('(No subject)'));
341
342
343
344
345
346
347
348
349
350
351
352
      }
    }

    return $comment;
  }

  /**
   * Form submission handler for the 'preview' action.
   *
   * @param $form
   *   An associative array containing the structure of the form.
   * @param $form_state
353
   *   The current state of the form.
354
   */
355
  public function preview(array &$form, FormStateInterface $form_state) {
356
    $comment = $this->entity;
357
    $form_state['comment_preview'] = comment_preview($comment, $form_state);
358
    $form_state['comment_preview']['#title'] = $this->t('Preview comment');
359
360
361
362
    $form_state['rebuild'] = TRUE;
  }

  /**
363
   * Overrides Drupal\Core\Entity\EntityForm::save().
364
   */
365
  public function save(array $form, FormStateInterface $form_state) {
366
    $comment = $this->entity;
367
    $entity = $comment->getCommentedEntity();
368
    $field_name = $comment->getFieldName();
369
    $uri = $entity->urlInfo();
370
    $logger = $this->logger('content');
371

372
    if ($this->currentUser->hasPermission('post comments') && ($this->currentUser->hasPermission('administer comments') || $entity->{$field_name}->status == CommentItemInterface::OPEN)) {
373
      $comment->save();
374
      $form_state->setValue('cid', $comment->id());
375

376
377
      // Add a log entry.
      $logger->notice('Comment posted: %subject.', array('%subject' => $comment->getSubject(), 'link' => l(t('View'), 'comment/' . $comment->id(), array('fragment' => 'comment-' . $comment->id()))));
378
379

      // Explain the approval queue if necessary.
380
      if (!$comment->isPublished()) {
381
382
        if (!$this->currentUser->hasPermission('administer comments')) {
          drupal_set_message($this->t('Your comment has been queued for review by site administrators and will be published after approval.'));
383
384
385
        }
      }
      else {
386
        drupal_set_message($this->t('Your comment has been posted.'));
387
388
389
      }
      $query = array();
      // Find the current display page for this comment.
390
      $field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$field_name];
391
      $page = $this->entityManager->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
392
393
394
395
      if ($page > 0) {
        $query['page'] = $page;
      }
      // Redirect to the newly posted comment.
396
397
      $uri->setOption('query', $query);
      $uri->setOption('fragment', 'comment-' . $comment->id());
398
399
    }
    else {
400
      $logger->warning('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->getSubject()));
401
      drupal_set_message($this->t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->getSubject())), 'error');
402
      // Redirect the user to the entity they are commenting on.
403
    }
404
    $form_state->setRedirectUrl($uri);
405
406
  }
}