summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/comment/comment.module
blob: b357f3069aebdebabd9b629fe58ad4f90dfed6a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
<?php

/**
 * @file
 */

use Drupal\comment\CommentInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Link;
use Drupal\Core\Url;

/**
 * The time cutoff for comments marked as read for entity types other node.
 *
 * Comments changed before this time are always marked as read.
 * Comments changed after this time may be marked new, updated, or read,
 * depending on their state for the current user. Defaults to 30 days ago.
 *
 * @todo Remove when https://www.drupal.org/node/2006632 lands.
 */
define('COMMENT_NEW_LIMIT', ((int) $_SERVER['REQUEST_TIME']) - 30 * 24 * 60 * 60);

/**
 * Entity URI callback.
 */
function comment_uri(CommentInterface $comment) {
  return new Url(
    'entity.comment.canonical',
    [
      'comment' => $comment->id(),
    ],
    ['fragment' => 'comment-' . $comment->id()]
  );
}

/**
 * Determines if an entity type is using an integer-based ID definition.
 *
 * @param string $entity_type_id
 *   The ID the represents the entity type.
 *
 * @return bool
 *   Returns TRUE if the entity type has an integer-based ID definition and
 *   FALSE otherwise.
 */
function _comment_entity_uses_integer_id($entity_type_id) {
  $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
  $entity_type_id_key = $entity_type->getKey('id');
  if ($entity_type_id_key === FALSE) {
    return FALSE;
  }
  $field_definitions = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type->id());
  $entity_type_id_definition = $field_definitions[$entity_type_id_key];
  return $entity_type_id_definition->getType() === 'integer';
}

/**
 * Generates a comment preview.
 *
 * @param \Drupal\comment\CommentInterface $comment
 *   The comment entity to preview.
 * @param Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
 *
 * @return array
 *   An array as expected by \Drupal\Core\Render\RendererInterface::render().
 */
function comment_preview(CommentInterface $comment, FormStateInterface $form_state): array {
  $preview_build = [];
  $entity = $comment->getCommentedEntity();

  if (!$form_state->getErrors()) {
    $comment->in_preview = TRUE;
    $comment_build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($comment);
    $comment_build['#weight'] = -100;

    $preview_build['comment_preview'] = $comment_build;
  }

  if ($comment->hasParentComment()) {
    $build = [];
    $parent = $comment->getParentComment();
    if ($parent && $parent->isPublished()) {
      $build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($parent);
    }
  }
  else {
    // The comment field output includes rendering the parent entity of the
    // thread to which the comment is a reply. The rendered entity output
    // includes the comment reply form, which contains the comment preview and
    // therefore the rendered parent entity. This results in an infinite loop of
    // parent entity output rendering the comment form and the comment form
    // rendering the parent entity. To prevent this infinite loop we temporarily
    // set the value of the comment field on a clone of the entity to hidden
    // before calling the entity view builder. That way when the output of
    // the commented entity is rendered, it excludes the comment field output.
    $field_name = $comment->getFieldName();
    $entity = clone $entity;
    $entity->$field_name->status = CommentItemInterface::HIDDEN;
    $build = \Drupal::entityTypeManager()
      ->getViewBuilder($entity->getEntityTypeId())
      ->view($entity, 'full');
  }

  $preview_build['comment_output_below'] = $build;
  $preview_build['comment_output_below']['#weight'] = 200;

  return $preview_build;
}

/**
 * Implements hook_preprocess_HOOK() for block templates.
 */
function comment_preprocess_block(&$variables): void {
  if ($variables['configuration']['provider'] == 'comment') {
    $variables['attributes']['role'] = 'navigation';
  }
}

/**
 * Prepares variables for comment templates.
 *
 * By default this function performs special preprocessing of some base fields
 * so they are available as variables in the template. For example 'subject'
 * appears as 'title'. This preprocessing is skipped if:
 * - a module makes the field's display configurable via the field UI by means
 *   of BaseFieldDefinition::setDisplayConfigurable()
 * - AND the additional entity type property
 *   'enable_base_field_custom_preprocess_skipping' has been set using
 *   hook_entity_type_build().
 *
 * Default template: comment.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the comment and entity objects.
 *     Array keys: #comment, #commented_entity.
 */
function template_preprocess_comment(&$variables): void {
  /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
  $date_formatter = \Drupal::service('date.formatter');
  /** @var \Drupal\comment\CommentInterface $comment */
  $comment = $variables['elements']['#comment'];
  $commented_entity = $comment->getCommentedEntity();
  $variables['comment'] = $comment;
  $variables['commented_entity'] = $commented_entity;
  $variables['threaded'] = $variables['elements']['#comment_threaded'];

  $skip_custom_preprocessing = $comment->getEntityType()->get('enable_base_field_custom_preprocess_skipping');

  // Make created, uid, pid and subject fields available separately. Skip this
  // custom preprocessing if the field display is configurable and skipping has
  // been enabled.
  // @todo https://www.drupal.org/project/drupal/issues/3015623
  //   Eventually delete this code and matching template lines. Using
  //   $variables['content'] is more flexible and consistent.
  $submitted_configurable = $comment->getFieldDefinition('created')->isDisplayConfigurable('view') || $comment->getFieldDefinition('uid')->isDisplayConfigurable('view');

  if (!$skip_custom_preprocessing || !$submitted_configurable) {
    $account = $comment->getOwner();
    $username = [
      '#theme' => 'username',
      '#account' => $account,
    ];
    $variables['author'] = \Drupal::service('renderer')->render($username);
    $variables['author_id'] = $comment->getOwnerId();
    $variables['new_indicator_timestamp'] = $comment->getChangedTime();
    $variables['created'] = $date_formatter->format($comment->getCreatedTime());
    // Avoid calling DateFormatterInterface::format() twice on the same
    // timestamp.
    if ($comment->getChangedTime() == $comment->getCreatedTime()) {
      $variables['changed'] = $variables['created'];
    }
    else {
      $variables['changed'] = $date_formatter->format($comment->getChangedTime());
    }

    if (theme_get_setting('features.comment_user_picture')) {
      // To change user picture settings (for instance, image style), edit the
      // 'compact' view mode on the User entity.
      $variables['user_picture'] = \Drupal::entityTypeManager()
        ->getViewBuilder('user')
        ->view($account, 'compact');
    }
    else {
      $variables['user_picture'] = [];
    }

    $variables['submitted'] = t('Submitted by @username on @datetime', ['@username' => $variables['author'], '@datetime' => $variables['created']]);
  }

  if (isset($comment->in_preview)) {
    $variables['permalink'] = Link::fromTextAndUrl(t('Permalink'), Url::fromRoute('<front>'))->toString();
  }
  else {
    $variables['permalink'] = Link::fromTextAndUrl(t('Permalink'), $comment->permalink())->toString();
  }

  if (($comment_parent = $comment->getParentComment()) && (!$skip_custom_preprocessing || !$comment->getFieldDefinition('pid')->isDisplayConfigurable('view'))) {
    // Fetch and store the parent comment information for use in templates.
    $account_parent = $comment_parent->getOwner();
    $variables['parent_comment'] = $comment_parent;
    $username = [
      '#theme' => 'username',
      '#account' => $account_parent,
    ];
    $variables['parent_author'] = \Drupal::service('renderer')->render($username);
    $variables['parent_created'] = $date_formatter->format($comment_parent->getCreatedTime());
    // Avoid calling DateFormatterInterface::format() twice on same timestamp.
    if ($comment_parent->getChangedTime() == $comment_parent->getCreatedTime()) {
      $variables['parent_changed'] = $variables['parent_created'];
    }
    else {
      $variables['parent_changed'] = $date_formatter->format($comment_parent->getChangedTime());
    }
    $permalink_uri_parent = $comment_parent->permalink();
    $attributes = $permalink_uri_parent->getOption('attributes') ?: [];
    $attributes += ['class' => ['permalink'], 'rel' => 'bookmark'];
    $permalink_uri_parent->setOption('attributes', $attributes);
    $variables['parent_title'] = Link::fromTextAndUrl($comment_parent->getSubject(), $permalink_uri_parent)->toString();
    $variables['parent_permalink'] = Link::fromTextAndUrl(t('Parent permalink'), $permalink_uri_parent)->toString();
    $variables['parent'] = t('In reply to @parent_title by @parent_username',
        ['@parent_username' => $variables['parent_author'], '@parent_title' => $variables['parent_title']]);
  }
  else {
    $variables['parent_comment'] = '';
    $variables['parent_author'] = '';
    $variables['parent_created'] = '';
    $variables['parent_changed'] = '';
    $variables['parent_title'] = '';
    $variables['parent_permalink'] = '';
    $variables['parent'] = '';
  }

  if (!$skip_custom_preprocessing || !$comment->getFieldDefinition('subject')->isDisplayConfigurable('view')) {
    if (isset($comment->in_preview)) {
      $variables['title'] = Link::fromTextAndUrl($comment->getSubject(), Url::fromRoute('<front>'))->toString();
    }
    else {
      $uri = $comment->permalink();
      $attributes = $uri->getOption('attributes') ?: [];
      $attributes += ['class' => ['permalink'], 'rel' => 'bookmark'];
      $uri->setOption('attributes', $attributes);
      $variables['title'] = Link::fromTextAndUrl($comment->getSubject(), $uri)->toString();
    }
  }

  // Helpful $content variable for templates.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }

  // Set status to a string representation of comment->status.
  if (isset($comment->in_preview)) {
    $variables['status'] = 'preview';
  }
  else {
    $variables['status'] = $comment->isPublished() ? 'published' : 'unpublished';
  }

  // Add comment author user ID. Necessary for the comment-by-viewer library.
  $variables['attributes']['data-comment-user-id'] = $comment->getOwnerId();
  // Add anchor for each comment.
  $variables['attributes']['id'] = 'comment-' . $comment->id();
}

/**
 * Prepares variables for comment field templates.
 *
 * Default template: field--comment.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: An associative array containing render arrays for the list of
 *     comments, and the comment form. Array keys: comments, comment_form.
 *
 * @todo Rename to template_preprocess_field__comment() once
 *   https://www.drupal.org/node/939462 is resolved.
 */
function comment_preprocess_field(&$variables): void {
  $element = $variables['element'];
  if ($element['#field_type'] == 'comment') {
    // Provide contextual information.
    $variables['comment_display_mode'] = $element[0]['#comment_display_mode'];
    $variables['comment_type'] = $element[0]['#comment_type'];

    // Append additional attributes from the first field item.
    $variables['attributes'] += $variables['items'][0]['attributes']->storage();

    // Create separate variables for the comments and comment form.
    $variables['comments'] = $element[0]['comments'];
    $variables['comment_form'] = $element[0]['comment_form'];
  }
}