annotate core/modules/comment/src/Controller/CommentController.php @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children 12f9dff5fda9
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\comment\Controller;
Chris@0 4
Chris@0 5 use Drupal\comment\CommentInterface;
Chris@0 6 use Drupal\comment\CommentManagerInterface;
Chris@0 7 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
Chris@0 8 use Drupal\Core\Access\AccessResult;
Chris@0 9 use Drupal\Core\Cache\CacheableResponseInterface;
Chris@0 10 use Drupal\Core\Controller\ControllerBase;
Chris@0 11 use Drupal\Core\Entity\EntityInterface;
Chris@0 12 use Drupal\Core\Entity\EntityManagerInterface;
Chris@0 13 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 14 use Symfony\Component\HttpFoundation\JsonResponse;
Chris@0 15 use Symfony\Component\HttpFoundation\RedirectResponse;
Chris@0 16 use Symfony\Component\HttpFoundation\Request;
Chris@0 17 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
Chris@0 18 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Chris@0 19 use Symfony\Component\HttpKernel\HttpKernelInterface;
Chris@0 20
Chris@0 21 /**
Chris@0 22 * Controller for the comment entity.
Chris@0 23 *
Chris@0 24 * @see \Drupal\comment\Entity\Comment.
Chris@0 25 */
Chris@0 26 class CommentController extends ControllerBase {
Chris@0 27
Chris@0 28 /**
Chris@0 29 * The HTTP kernel.
Chris@0 30 *
Chris@0 31 * @var \Symfony\Component\HttpKernel\HttpKernelInterface
Chris@0 32 */
Chris@0 33 protected $httpKernel;
Chris@0 34
Chris@0 35 /**
Chris@0 36 * The comment manager service.
Chris@0 37 *
Chris@0 38 * @var \Drupal\comment\CommentManagerInterface
Chris@0 39 */
Chris@0 40 protected $commentManager;
Chris@0 41
Chris@0 42 /**
Chris@0 43 * The entity manager.
Chris@0 44 *
Chris@0 45 * @var \Drupal\Core\Entity\EntityStorageInterface
Chris@0 46 */
Chris@0 47 protected $entityManager;
Chris@0 48
Chris@0 49 /**
Chris@0 50 * Constructs a CommentController object.
Chris@0 51 *
Chris@0 52 * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
Chris@0 53 * HTTP kernel to handle requests.
Chris@0 54 * @param \Drupal\comment\CommentManagerInterface $comment_manager
Chris@0 55 * The comment manager service.
Chris@0 56 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
Chris@0 57 * The entity manager service.
Chris@0 58 */
Chris@0 59 public function __construct(HttpKernelInterface $http_kernel, CommentManagerInterface $comment_manager, EntityManagerInterface $entity_manager) {
Chris@0 60 $this->httpKernel = $http_kernel;
Chris@0 61 $this->commentManager = $comment_manager;
Chris@0 62 $this->entityManager = $entity_manager;
Chris@0 63 }
Chris@0 64
Chris@0 65 /**
Chris@0 66 * {@inheritdoc}
Chris@0 67 */
Chris@0 68 public static function create(ContainerInterface $container) {
Chris@0 69 return new static(
Chris@0 70 $container->get('http_kernel'),
Chris@0 71 $container->get('comment.manager'),
Chris@0 72 $container->get('entity.manager')
Chris@0 73 );
Chris@0 74 }
Chris@0 75
Chris@0 76 /**
Chris@0 77 * Publishes the specified comment.
Chris@0 78 *
Chris@0 79 * @param \Drupal\comment\CommentInterface $comment
Chris@0 80 * A comment entity.
Chris@0 81 *
Chris@0 82 * @return \Symfony\Component\HttpFoundation\RedirectResponse
Chris@0 83 */
Chris@0 84 public function commentApprove(CommentInterface $comment) {
Chris@4 85 $comment->setPublished();
Chris@0 86 $comment->save();
Chris@0 87
Chris@4 88 $this->messenger()->addStatus($this->t('Comment approved.'));
Chris@0 89 $permalink_uri = $comment->permalink();
Chris@0 90 $permalink_uri->setAbsolute();
Chris@0 91 return new RedirectResponse($permalink_uri->toString());
Chris@0 92 }
Chris@0 93
Chris@0 94 /**
Chris@0 95 * Redirects comment links to the correct page depending on comment settings.
Chris@0 96 *
Chris@0 97 * Since comments are paged there is no way to guarantee which page a comment
Chris@0 98 * appears on. Comment paging and threading settings may be changed at any
Chris@0 99 * time. With threaded comments, an individual comment may move between pages
Chris@0 100 * as comments can be added either before or after it in the overall
Chris@0 101 * discussion. Therefore we use a central routing function for comment links,
Chris@0 102 * which calculates the page number based on current comment settings and
Chris@0 103 * returns the full comment view with the pager set dynamically.
Chris@0 104 *
Chris@0 105 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 106 * The request of the page.
Chris@0 107 * @param \Drupal\comment\CommentInterface $comment
Chris@0 108 * A comment entity.
Chris@0 109 *
Chris@0 110 * @return \Symfony\Component\HttpFoundation\Response
Chris@0 111 * The comment listing set to the page on which the comment appears.
Chris@0 112 *
Chris@0 113 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 114 * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
Chris@0 115 */
Chris@0 116 public function commentPermalink(Request $request, CommentInterface $comment) {
Chris@0 117 if ($entity = $comment->getCommentedEntity()) {
Chris@0 118 // Check access permissions for the entity.
Chris@0 119 if (!$entity->access('view')) {
Chris@0 120 throw new AccessDeniedHttpException();
Chris@0 121 }
Chris@0 122 $field_definition = $this->entityManager()->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
Chris@0 123
Chris@0 124 // Find the current display page for this comment.
Chris@0 125 $page = $this->entityManager()->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
Chris@0 126 // @todo: Cleaner sub request handling.
Chris@0 127 $subrequest_url = $entity->urlInfo()->setOption('query', ['page' => $page])->toString(TRUE);
Chris@0 128 $redirect_request = Request::create($subrequest_url->getGeneratedUrl(), 'GET', $request->query->all(), $request->cookies->all(), [], $request->server->all());
Chris@0 129 // Carry over the session to the subrequest.
Chris@0 130 if ($session = $request->getSession()) {
Chris@0 131 $redirect_request->setSession($session);
Chris@0 132 }
Chris@0 133 $request->query->set('page', $page);
Chris@0 134 $response = $this->httpKernel->handle($redirect_request, HttpKernelInterface::SUB_REQUEST);
Chris@0 135 if ($response instanceof CacheableResponseInterface) {
Chris@0 136 // @todo Once path aliases have cache tags (see
Chris@0 137 // https://www.drupal.org/node/2480077), add test coverage that
Chris@0 138 // the cache tag for a commented entity's path alias is added to the
Chris@0 139 // comment's permalink response, because there can be blocks or
Chris@0 140 // other content whose renderings depend on the subrequest's URL.
Chris@0 141 $response->addCacheableDependency($subrequest_url);
Chris@0 142 }
Chris@0 143 return $response;
Chris@0 144 }
Chris@0 145 throw new NotFoundHttpException();
Chris@0 146 }
Chris@0 147
Chris@0 148 /**
Chris@0 149 * The _title_callback for the page that renders the comment permalink.
Chris@0 150 *
Chris@0 151 * @param \Drupal\comment\CommentInterface $comment
Chris@0 152 * The current comment.
Chris@0 153 *
Chris@0 154 * @return string
Chris@0 155 * The translated comment subject.
Chris@0 156 */
Chris@0 157 public function commentPermalinkTitle(CommentInterface $comment) {
Chris@0 158 return $this->entityManager()->getTranslationFromContext($comment)->label();
Chris@0 159 }
Chris@0 160
Chris@0 161 /**
Chris@0 162 * Redirects legacy node links to the new path.
Chris@0 163 *
Chris@0 164 * @param \Drupal\Core\Entity\EntityInterface $node
Chris@0 165 * The node object identified by the legacy URL.
Chris@0 166 *
Chris@0 167 * @return \Symfony\Component\HttpFoundation\RedirectResponse
Chris@0 168 * Redirects user to new url.
Chris@0 169 *
Chris@0 170 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 171 */
Chris@0 172 public function redirectNode(EntityInterface $node) {
Chris@0 173 $fields = $this->commentManager->getFields('node');
Chris@0 174 // Legacy nodes only had a single comment field, so use the first comment
Chris@0 175 // field on the entity.
Chris@0 176 if (!empty($fields) && ($field_names = array_keys($fields)) && ($field_name = reset($field_names))) {
Chris@0 177 return $this->redirect('comment.reply', [
Chris@0 178 'entity_type' => 'node',
Chris@0 179 'entity' => $node->id(),
Chris@0 180 'field_name' => $field_name,
Chris@0 181 ]);
Chris@0 182 }
Chris@0 183 throw new NotFoundHttpException();
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Form constructor for the comment reply form.
Chris@0 188 *
Chris@0 189 * There are several cases that have to be handled, including:
Chris@0 190 * - replies to comments
Chris@0 191 * - replies to entities
Chris@0 192 *
Chris@0 193 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 194 * The current request object.
Chris@0 195 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 196 * The entity this comment belongs to.
Chris@0 197 * @param string $field_name
Chris@0 198 * The field_name to which the comment belongs.
Chris@0 199 * @param int $pid
Chris@0 200 * (optional) Some comments are replies to other comments. In those cases,
Chris@0 201 * $pid is the parent comment's comment ID. Defaults to NULL.
Chris@0 202 *
Chris@0 203 * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
Chris@0 204 * An associative array containing:
Chris@0 205 * - An array for rendering the entity or parent comment.
Chris@0 206 * - comment_entity: If the comment is a reply to the entity.
Chris@0 207 * - comment_parent: If the comment is a reply to another comment.
Chris@0 208 * - comment_form: The comment form as a renderable array.
Chris@0 209 *
Chris@0 210 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 211 */
Chris@0 212 public function getReplyForm(Request $request, EntityInterface $entity, $field_name, $pid = NULL) {
Chris@0 213 $account = $this->currentUser();
Chris@0 214 $build = [];
Chris@0 215
Chris@0 216 // The user is not just previewing a comment.
Chris@0 217 if ($request->request->get('op') != $this->t('Preview')) {
Chris@0 218
Chris@0 219 // $pid indicates that this is a reply to a comment.
Chris@0 220 if ($pid) {
Chris@0 221 // Load the parent comment.
Chris@0 222 $comment = $this->entityManager()->getStorage('comment')->load($pid);
Chris@0 223 // Display the parent comment.
Chris@0 224 $build['comment_parent'] = $this->entityManager()->getViewBuilder('comment')->view($comment);
Chris@0 225 }
Chris@0 226
Chris@0 227 // The comment is in response to a entity.
Chris@0 228 elseif ($entity->access('view', $account)) {
Chris@0 229 // We make sure the field value isn't set so we don't end up with a
Chris@0 230 // redirect loop.
Chris@0 231 $entity = clone $entity;
Chris@0 232 $entity->{$field_name}->status = CommentItemInterface::HIDDEN;
Chris@0 233 // Render array of the entity full view mode.
Chris@0 234 $build['commented_entity'] = $this->entityManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, 'full');
Chris@0 235 unset($build['commented_entity']['#cache']);
Chris@0 236 }
Chris@0 237 }
Chris@0 238 else {
Chris@0 239 $build['#title'] = $this->t('Preview comment');
Chris@0 240 }
Chris@0 241
Chris@0 242 // Show the actual reply box.
Chris@0 243 $comment = $this->entityManager()->getStorage('comment')->create([
Chris@0 244 'entity_id' => $entity->id(),
Chris@0 245 'pid' => $pid,
Chris@0 246 'entity_type' => $entity->getEntityTypeId(),
Chris@0 247 'field_name' => $field_name,
Chris@0 248 ]);
Chris@0 249 $build['comment_form'] = $this->entityFormBuilder()->getForm($comment);
Chris@0 250
Chris@0 251 return $build;
Chris@0 252 }
Chris@0 253
Chris@0 254 /**
Chris@0 255 * Access check for the reply form.
Chris@0 256 *
Chris@0 257 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@0 258 * The entity this comment belongs to.
Chris@0 259 * @param string $field_name
Chris@0 260 * The field_name to which the comment belongs.
Chris@0 261 * @param int $pid
Chris@0 262 * (optional) Some comments are replies to other comments. In those cases,
Chris@0 263 * $pid is the parent comment's comment ID. Defaults to NULL.
Chris@0 264 *
Chris@0 265 * @return \Drupal\Core\Access\AccessResultInterface
Chris@0 266 * An access result
Chris@0 267 *
Chris@0 268 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 269 */
Chris@0 270 public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NULL) {
Chris@0 271 // Check if entity and field exists.
Chris@0 272 $fields = $this->commentManager->getFields($entity->getEntityTypeId());
Chris@0 273 if (empty($fields[$field_name])) {
Chris@0 274 throw new NotFoundHttpException();
Chris@0 275 }
Chris@0 276
Chris@0 277 $account = $this->currentUser();
Chris@0 278
Chris@0 279 // Check if the user has the proper permissions.
Chris@0 280 $access = AccessResult::allowedIfHasPermission($account, 'post comments');
Chris@0 281
Chris@0 282 // If commenting is open on the entity.
Chris@0 283 $status = $entity->{$field_name}->status;
Chris@0 284 $access = $access->andIf(AccessResult::allowedIf($status == CommentItemInterface::OPEN)
Chris@0 285 ->addCacheableDependency($entity))
Chris@0 286 // And if user has access to the host entity.
Chris@0 287 ->andIf(AccessResult::allowedIf($entity->access('view')));
Chris@0 288
Chris@0 289 // $pid indicates that this is a reply to a comment.
Chris@0 290 if ($pid) {
Chris@0 291 // Check if the user has the proper permissions.
Chris@0 292 $access = $access->andIf(AccessResult::allowedIfHasPermission($account, 'access comments'));
Chris@0 293
Chris@0 294 // Load the parent comment.
Chris@0 295 $comment = $this->entityManager()->getStorage('comment')->load($pid);
Chris@0 296 // Check if the parent comment is published and belongs to the entity.
Chris@0 297 $access = $access->andIf(AccessResult::allowedIf($comment && $comment->isPublished() && $comment->getCommentedEntityId() == $entity->id()));
Chris@0 298 if ($comment) {
Chris@0 299 $access->addCacheableDependency($comment);
Chris@0 300 }
Chris@0 301 }
Chris@0 302 return $access;
Chris@0 303 }
Chris@0 304
Chris@0 305 /**
Chris@0 306 * Returns a set of nodes' last read timestamps.
Chris@0 307 *
Chris@0 308 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 309 * The request of the page.
Chris@0 310 *
Chris@0 311 * @return \Symfony\Component\HttpFoundation\JsonResponse
Chris@0 312 * The JSON response.
Chris@0 313 *
Chris@0 314 * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
Chris@0 315 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 316 */
Chris@0 317 public function renderNewCommentsNodeLinks(Request $request) {
Chris@0 318 if ($this->currentUser()->isAnonymous()) {
Chris@0 319 throw new AccessDeniedHttpException();
Chris@0 320 }
Chris@0 321
Chris@0 322 $nids = $request->request->get('node_ids');
Chris@0 323 $field_name = $request->request->get('field_name');
Chris@0 324 if (!isset($nids)) {
Chris@0 325 throw new NotFoundHttpException();
Chris@0 326 }
Chris@0 327 // Only handle up to 100 nodes.
Chris@0 328 $nids = array_slice($nids, 0, 100);
Chris@0 329
Chris@0 330 $links = [];
Chris@0 331 foreach ($nids as $nid) {
Chris@0 332 $node = $this->entityManager->getStorage('node')->load($nid);
Chris@0 333 $new = $this->commentManager->getCountNewComments($node);
Chris@0 334 $page_number = $this->entityManager()->getStorage('comment')
Chris@0 335 ->getNewCommentPageNumber($node->{$field_name}->comment_count, $new, $node, $field_name);
Chris@0 336 $query = $page_number ? ['page' => $page_number] : NULL;
Chris@0 337 $links[$nid] = [
Chris@0 338 'new_comment_count' => (int) $new,
Chris@0 339 'first_new_comment_link' => $this->getUrlGenerator()->generateFromRoute('entity.node.canonical', ['node' => $node->id()], ['query' => $query, 'fragment' => 'new']),
Chris@0 340 ];
Chris@0 341 }
Chris@0 342
Chris@0 343 return new JsonResponse($links);
Chris@0 344 }
Chris@0 345
Chris@0 346 }