summaryrefslogtreecommitdiffstats
path: root/core/modules/comment/src/Controller/CommentController.php
blob: 560233889cccc69f6e042f56a2d2ff927d2fd846 (plain)
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
<?php

namespace Drupal\comment\Controller;

use Drupal\comment\CommentInterface;
use Drupal\comment\CommentManagerInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;

/**
 * Controller for the comment entity.
 *
 * @see \Drupal\comment\Entity\Comment.
 */
class CommentController extends ControllerBase {

  /**
   * The HTTP kernel.
   *
   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
   */
  protected $httpKernel;

  /**
   * The comment manager service.
   *
   * @var \Drupal\comment\CommentManagerInterface
   */
  protected $commentManager;

  /**
   * The entity manager.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $entityManager;

  /**
   * Constructs a CommentController object.
   *
   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
   *   HTTP kernel to handle requests.
   * @param \Drupal\comment\CommentManagerInterface $comment_manager
   *   The comment manager service.
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
   *   The entity manager service.
   */
  public function __construct(HttpKernelInterface $http_kernel, CommentManagerInterface $comment_manager, EntityManagerInterface $entity_manager) {
    $this->httpKernel = $http_kernel;
    $this->commentManager = $comment_manager;
    $this->entityManager = $entity_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('http_kernel'),
      $container->get('comment.manager'),
      $container->get('entity.manager')
    );
  }

  /**
   * Publishes the specified comment.
   *
   * @param \Drupal\comment\CommentInterface $comment
   *   A comment entity.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   */
  public function commentApprove(CommentInterface $comment) {
    $comment->setPublished(TRUE);
    $comment->save();

    drupal_set_message($this->t('Comment approved.'));
    $permalink_uri = $comment->permalink();
    $permalink_uri->setAbsolute();
    return new RedirectResponse($permalink_uri->toString());
  }

  /**
   * Redirects comment links to the correct page depending on comment settings.
   *
   * Since comments are paged there is no way to guarantee which page a comment
   * appears on. Comment paging and threading settings may be changed at any
   * time. With threaded comments, an individual comment may move between pages
   * as comments can be added either before or after it in the overall
   * discussion. Therefore we use a central routing function for comment links,
   * which calculates the page number based on current comment settings and
   * returns the full comment view with the pager set dynamically.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request of the page.
   * @param \Drupal\comment\CommentInterface $comment
   *   A comment entity.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   The comment listing set to the page on which the comment appears.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
   */
  public function commentPermalink(Request $request, CommentInterface $comment) {
    if ($entity = $comment->getCommentedEntity()) {
      // Check access permissions for the entity.
      if (!$entity->access('view')) {
        throw new AccessDeniedHttpException();
      }
      $field_definition = $this->entityManager()->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];

      // Find the current display page for this comment.
      $page = $this->entityManager()->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
      // @todo: Cleaner sub request handling.
      $subrequest_url = $entity->urlInfo()->setOption('query', ['page' => $page])->toString(TRUE);
      $redirect_request = Request::create($subrequest_url->getGeneratedUrl(), 'GET', $request->query->all(), $request->cookies->all(), [], $request->server->all());
      // Carry over the session to the subrequest.
      if ($session = $request->getSession()) {
        $redirect_request->setSession($session);
      }
      $request->query->set('page', $page);
      $response = $this->httpKernel->handle($redirect_request, HttpKernelInterface::SUB_REQUEST);
      if ($response instanceof CacheableResponseInterface) {
        // @todo Once path aliases have cache tags (see
        //   https://www.drupal.org/node/2480077), add test coverage that
        //   the cache tag for a commented entity's path alias is added to the
        //   comment's permalink response, because there can be blocks or
        //   other content whose renderings depend on the subrequest's URL.
        $response->addCacheableDependency($subrequest_url);
      }
      return $response;
    }
    throw new NotFoundHttpException();
  }

  /**
   * The _title_callback for the page that renders the comment permalink.
   *
   * @param \Drupal\comment\CommentInterface $comment
   *   The current comment.
   *
   * @return string
   *   The translated comment subject.
   */
  public function commentPermalinkTitle(CommentInterface $comment) {
    return $this->entityManager()->getTranslationFromContext($comment)->label();
  }

  /**
   * Redirects legacy node links to the new path.
   *
   * @param \Drupal\Core\Entity\EntityInterface $node
   *   The node object identified by the legacy URL.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   *   Redirects user to new url.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   */
  public function redirectNode(EntityInterface $node) {
    $fields = $this->commentManager->getFields('node');
    // Legacy nodes only had a single comment field, so use the first comment
    // field on the entity.
    if (!empty($fields) && ($field_names = array_keys($fields)) && ($field_name = reset($field_names))) {
      return $this->redirect('comment.reply', [
        'entity_type' => 'node',
        'entity' => $node->id(),
        'field_name' => $field_name,
      ]);
    }
    throw new NotFoundHttpException();
  }

  /**
   * Form constructor for the comment reply form.
   *
   * There are several cases that have to be handled, including:
   *   - replies to comments
   *   - replies to entities
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request object.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity this comment belongs to.
   * @param string $field_name
   *   The field_name to which the comment belongs.
   * @param int $pid
   *   (optional) Some comments are replies to other comments. In those cases,
   *   $pid is the parent comment's comment ID. Defaults to NULL.
   *
   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
   *   An associative array containing:
   *   - An array for rendering the entity or parent comment.
   *     - comment_entity: If the comment is a reply to the entity.
   *     - comment_parent: If the comment is a reply to another comment.
   *   - comment_form: The comment form as a renderable array.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   */
  public function getReplyForm(Request $request, EntityInterface $entity, $field_name, $pid = NULL) {
    $account = $this->currentUser();
    $build = [];

    // The user is not just previewing a comment.
    if ($request->request->get('op') != $this->t('Preview')) {

      // $pid indicates that this is a reply to a comment.
      if ($pid) {
        // Load the parent comment.
        $comment = $this->entityManager()->getStorage('comment')->load($pid);
        // Display the parent comment.
        $build['comment_parent'] = $this->entityManager()->getViewBuilder('comment')->view($comment);
      }

      // The comment is in response to a entity.
      elseif ($entity->access('view', $account)) {
        // We make sure the field value isn't set so we don't end up with a
        // redirect loop.
        $entity = clone $entity;
        $entity->{$field_name}->status = CommentItemInterface::HIDDEN;
        // Render array of the entity full view mode.
        $build['commented_entity'] = $this->entityManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, 'full');
        unset($build['commented_entity']['#cache']);
      }
    }
    else {
      $build['#title'] = $this->t('Preview comment');
    }

    // Show the actual reply box.
    $comment = $this->entityManager()->getStorage('comment')->create([
      'entity_id' => $entity->id(),
      'pid' => $pid,
      'entity_type' => $entity->getEntityTypeId(),
      'field_name' => $field_name,
    ]);
    $build['comment_form'] = $this->entityFormBuilder()->getForm($comment);

    return $build;
  }

  /**
   * Access check for the reply form.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity this comment belongs to.
   * @param string $field_name
   *   The field_name to which the comment belongs.
   * @param int $pid
   *   (optional) Some comments are replies to other comments. In those cases,
   *   $pid is the parent comment's comment ID. Defaults to NULL.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   An access result
   *
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   */
  public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NULL) {
    // Check if entity and field exists.
    $fields = $this->commentManager->getFields($entity->getEntityTypeId());
    if (empty($fields[$field_name])) {
      throw new NotFoundHttpException();
    }

    $account = $this->currentUser();

    // Check if the user has the proper permissions.
    $access = AccessResult::allowedIfHasPermission($account, 'post comments');

    // If commenting is open on the entity.
    $status = $entity->{$field_name}->status;
    $access = $access->andIf(AccessResult::allowedIf($status == CommentItemInterface::OPEN)
      ->addCacheableDependency($entity))
      // And if user has access to the host entity.
      ->andIf(AccessResult::allowedIf($entity->access('view')));

    // $pid indicates that this is a reply to a comment.
    if ($pid) {
      // Check if the user has the proper permissions.
      $access = $access->andIf(AccessResult::allowedIfHasPermission($account, 'access comments'));

      // Load the parent comment.
      $comment = $this->entityManager()->getStorage('comment')->load($pid);
      // Check if the parent comment is published and belongs to the entity.
      $access = $access->andIf(AccessResult::allowedIf($comment && $comment->isPublished() && $comment->getCommentedEntityId() == $entity->id()));
      if ($comment) {
        $access->addCacheableDependency($comment);
      }
    }
    return $access;
  }

  /**
   * Returns a set of nodes' last read timestamps.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request of the page.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The JSON response.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   */
  public function renderNewCommentsNodeLinks(Request $request) {
    if ($this->currentUser()->isAnonymous()) {
      throw new AccessDeniedHttpException();
    }

    $nids = $request->request->get('node_ids');
    $field_name = $request->request->get('field_name');
    if (!isset($nids)) {
      throw new NotFoundHttpException();
    }
    // Only handle up to 100 nodes.
    $nids = array_slice($nids, 0, 100);

    $links = [];
    foreach ($nids as $nid) {
      $node = $this->entityManager->getStorage('node')->load($nid);
      $new = $this->commentManager->getCountNewComments($node);
      $page_number = $this->entityManager()->getStorage('comment')
        ->getNewCommentPageNumber($node->{$field_name}->comment_count, $new, $node, $field_name);
      $query = $page_number ? ['page' => $page_number] : NULL;
      $links[$nid] = [
        'new_comment_count' => (int) $new,
        'first_new_comment_link' => $this->getUrlGenerator()->generateFromRoute('entity.node.canonical', ['node' => $node->id()], ['query' => $query, 'fragment' => 'new']),
      ];
    }

    return new JsonResponse($links);
  }

}