annotate core/modules/node/src/Controller/NodeController.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\node\Controller;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Xss;
Chris@0 6 use Drupal\Core\Controller\ControllerBase;
Chris@0 7 use Drupal\Core\Datetime\DateFormatterInterface;
Chris@0 8 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
Chris@18 9 use Drupal\Core\Entity\EntityRepositoryInterface;
Chris@0 10 use Drupal\Core\Render\RendererInterface;
Chris@0 11 use Drupal\Core\Url;
Chris@0 12 use Drupal\node\NodeStorageInterface;
Chris@0 13 use Drupal\node\NodeTypeInterface;
Chris@0 14 use Drupal\node\NodeInterface;
Chris@0 15 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Returns responses for Node routes.
Chris@0 19 */
Chris@0 20 class NodeController extends ControllerBase implements ContainerInjectionInterface {
Chris@0 21
Chris@0 22 /**
Chris@0 23 * The date formatter service.
Chris@0 24 *
Chris@0 25 * @var \Drupal\Core\Datetime\DateFormatterInterface
Chris@0 26 */
Chris@0 27 protected $dateFormatter;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * The renderer service.
Chris@0 31 *
Chris@0 32 * @var \Drupal\Core\Render\RendererInterface
Chris@0 33 */
Chris@0 34 protected $renderer;
Chris@0 35
Chris@0 36 /**
Chris@18 37 * The entity repository service.
Chris@18 38 *
Chris@18 39 * @var \Drupal\Core\Entity\EntityRepositoryInterface
Chris@18 40 */
Chris@18 41 protected $entityRepository;
Chris@18 42
Chris@18 43 /**
Chris@0 44 * Constructs a NodeController object.
Chris@0 45 *
Chris@0 46 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
Chris@0 47 * The date formatter service.
Chris@0 48 * @param \Drupal\Core\Render\RendererInterface $renderer
Chris@0 49 * The renderer service.
Chris@18 50 * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
Chris@18 51 * The entity repository.
Chris@0 52 */
Chris@18 53 public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, EntityRepositoryInterface $entity_repository = NULL) {
Chris@0 54 $this->dateFormatter = $date_formatter;
Chris@0 55 $this->renderer = $renderer;
Chris@18 56 if (!$entity_repository) {
Chris@18 57 @trigger_error('The entity.repository service must be passed to NodeController::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 58 $entity_repository = \Drupal::service('entity.repository');
Chris@18 59 }
Chris@18 60 $this->entityRepository = $entity_repository;
Chris@0 61 }
Chris@0 62
Chris@0 63 /**
Chris@0 64 * {@inheritdoc}
Chris@0 65 */
Chris@0 66 public static function create(ContainerInterface $container) {
Chris@0 67 return new static(
Chris@0 68 $container->get('date.formatter'),
Chris@18 69 $container->get('renderer'),
Chris@18 70 $container->get('entity.repository')
Chris@0 71 );
Chris@0 72 }
Chris@0 73
Chris@0 74 /**
Chris@0 75 * Displays add content links for available content types.
Chris@0 76 *
Chris@0 77 * Redirects to node/add/[type] if only one content type is available.
Chris@0 78 *
Chris@0 79 * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
Chris@0 80 * A render array for a list of the node types that can be added; however,
Chris@0 81 * if there is only one node type defined for the site, the function
Chris@0 82 * will return a RedirectResponse to the node add page for that one node
Chris@0 83 * type.
Chris@0 84 */
Chris@0 85 public function addPage() {
Chris@0 86 $build = [
Chris@0 87 '#theme' => 'node_add_list',
Chris@0 88 '#cache' => [
Chris@18 89 'tags' => $this->entityTypeManager()->getDefinition('node_type')->getListCacheTags(),
Chris@0 90 ],
Chris@0 91 ];
Chris@0 92
Chris@0 93 $content = [];
Chris@0 94
Chris@0 95 // Only use node types the user has access to.
Chris@18 96 foreach ($this->entityTypeManager()->getStorage('node_type')->loadMultiple() as $type) {
Chris@18 97 $access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
Chris@0 98 if ($access->isAllowed()) {
Chris@0 99 $content[$type->id()] = $type;
Chris@0 100 }
Chris@0 101 $this->renderer->addCacheableDependency($build, $access);
Chris@0 102 }
Chris@0 103
Chris@0 104 // Bypass the node/add listing if only one content type is available.
Chris@0 105 if (count($content) == 1) {
Chris@0 106 $type = array_shift($content);
Chris@0 107 return $this->redirect('node.add', ['node_type' => $type->id()]);
Chris@0 108 }
Chris@0 109
Chris@0 110 $build['#content'] = $content;
Chris@0 111
Chris@0 112 return $build;
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * Provides the node submission form.
Chris@0 117 *
Chris@0 118 * @param \Drupal\node\NodeTypeInterface $node_type
Chris@0 119 * The node type entity for the node.
Chris@0 120 *
Chris@0 121 * @return array
Chris@0 122 * A node submission form.
Chris@0 123 */
Chris@0 124 public function add(NodeTypeInterface $node_type) {
Chris@18 125 $node = $this->entityTypeManager()->getStorage('node')->create([
Chris@0 126 'type' => $node_type->id(),
Chris@0 127 ]);
Chris@0 128
Chris@0 129 $form = $this->entityFormBuilder()->getForm($node);
Chris@0 130
Chris@0 131 return $form;
Chris@0 132 }
Chris@0 133
Chris@0 134 /**
Chris@0 135 * Displays a node revision.
Chris@0 136 *
Chris@0 137 * @param int $node_revision
Chris@0 138 * The node revision ID.
Chris@0 139 *
Chris@0 140 * @return array
Chris@16 141 * An array suitable for \Drupal\Core\Render\RendererInterface::render().
Chris@0 142 */
Chris@0 143 public function revisionShow($node_revision) {
Chris@18 144 $node = $this->entityTypeManager()->getStorage('node')->loadRevision($node_revision);
Chris@18 145 $node = $this->entityRepository->getTranslationFromContext($node);
Chris@18 146 $node_view_controller = new NodeViewController($this->entityTypeManager(), $this->renderer, $this->currentUser(), $this->entityRepository);
Chris@0 147 $page = $node_view_controller->view($node);
Chris@0 148 unset($page['nodes'][$node->id()]['#cache']);
Chris@0 149 return $page;
Chris@0 150 }
Chris@0 151
Chris@0 152 /**
Chris@0 153 * Page title callback for a node revision.
Chris@0 154 *
Chris@0 155 * @param int $node_revision
Chris@0 156 * The node revision ID.
Chris@0 157 *
Chris@0 158 * @return string
Chris@0 159 * The page title.
Chris@0 160 */
Chris@0 161 public function revisionPageTitle($node_revision) {
Chris@18 162 $node = $this->entityTypeManager()->getStorage('node')->loadRevision($node_revision);
Chris@18 163 return $this->t('Revision of %title from %date', ['%title' => $node->label(), '%date' => $this->dateFormatter->format($node->getRevisionCreationTime())]);
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * Generates an overview table of older revisions of a node.
Chris@0 168 *
Chris@0 169 * @param \Drupal\node\NodeInterface $node
Chris@0 170 * A node object.
Chris@0 171 *
Chris@0 172 * @return array
Chris@16 173 * An array as expected by \Drupal\Core\Render\RendererInterface::render().
Chris@0 174 */
Chris@0 175 public function revisionOverview(NodeInterface $node) {
Chris@0 176 $account = $this->currentUser();
Chris@0 177 $langcode = $node->language()->getId();
Chris@0 178 $langname = $node->language()->getName();
Chris@0 179 $languages = $node->getTranslationLanguages();
Chris@0 180 $has_translations = (count($languages) > 1);
Chris@18 181 $node_storage = $this->entityTypeManager()->getStorage('node');
Chris@0 182 $type = $node->getType();
Chris@0 183
Chris@0 184 $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]);
Chris@0 185 $header = [$this->t('Revision'), $this->t('Operations')];
Chris@0 186
Chris@0 187 $revert_permission = (($account->hasPermission("revert $type revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'));
Chris@0 188 $delete_permission = (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'));
Chris@0 189
Chris@0 190 $rows = [];
Chris@0 191 $default_revision = $node->getRevisionId();
Chris@14 192 $current_revision_displayed = FALSE;
Chris@0 193
Chris@0 194 foreach ($this->getRevisionIds($node, $node_storage) as $vid) {
Chris@0 195 /** @var \Drupal\node\NodeInterface $revision */
Chris@0 196 $revision = $node_storage->loadRevision($vid);
Chris@0 197 // Only show revisions that are affected by the language that is being
Chris@0 198 // displayed.
Chris@0 199 if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
Chris@0 200 $username = [
Chris@0 201 '#theme' => 'username',
Chris@0 202 '#account' => $revision->getRevisionUser(),
Chris@0 203 ];
Chris@0 204
Chris@0 205 // Use revision link to link to revisions that are not active.
Chris@0 206 $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short');
Chris@14 207
Chris@14 208 // We treat also the latest translation-affecting revision as current
Chris@14 209 // revision, if it was the default revision, as its values for the
Chris@14 210 // current language will be the same of the current default revision in
Chris@14 211 // this case.
Chris@14 212 $is_current_revision = $vid == $default_revision || (!$current_revision_displayed && $revision->wasDefaultRevision());
Chris@14 213 if (!$is_current_revision) {
Chris@0 214 $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid]));
Chris@0 215 }
Chris@0 216 else {
Chris@18 217 $link = $node->toLink($date)->toString();
Chris@14 218 $current_revision_displayed = TRUE;
Chris@0 219 }
Chris@0 220
Chris@0 221 $row = [];
Chris@0 222 $column = [
Chris@0 223 'data' => [
Chris@0 224 '#type' => 'inline_template',
Chris@0 225 '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}',
Chris@0 226 '#context' => [
Chris@0 227 'date' => $link,
Chris@0 228 'username' => $this->renderer->renderPlain($username),
Chris@0 229 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()],
Chris@0 230 ],
Chris@0 231 ],
Chris@0 232 ];
Chris@0 233 // @todo Simplify once https://www.drupal.org/node/2334319 lands.
Chris@0 234 $this->renderer->addCacheableDependency($column['data'], $username);
Chris@0 235 $row[] = $column;
Chris@0 236
Chris@14 237 if ($is_current_revision) {
Chris@0 238 $row[] = [
Chris@0 239 'data' => [
Chris@0 240 '#prefix' => '<em>',
Chris@0 241 '#markup' => $this->t('Current revision'),
Chris@0 242 '#suffix' => '</em>',
Chris@0 243 ],
Chris@0 244 ];
Chris@0 245
Chris@0 246 $rows[] = [
Chris@0 247 'data' => $row,
Chris@0 248 'class' => ['revision-current'],
Chris@0 249 ];
Chris@0 250 }
Chris@0 251 else {
Chris@0 252 $links = [];
Chris@0 253 if ($revert_permission) {
Chris@0 254 $links['revert'] = [
Chris@0 255 'title' => $vid < $node->getRevisionId() ? $this->t('Revert') : $this->t('Set as current revision'),
Chris@0 256 'url' => $has_translations ?
Chris@0 257 Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) :
Chris@0 258 Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
Chris@0 259 ];
Chris@0 260 }
Chris@0 261
Chris@0 262 if ($delete_permission) {
Chris@0 263 $links['delete'] = [
Chris@0 264 'title' => $this->t('Delete'),
Chris@0 265 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
Chris@0 266 ];
Chris@0 267 }
Chris@0 268
Chris@0 269 $row[] = [
Chris@0 270 'data' => [
Chris@0 271 '#type' => 'operations',
Chris@0 272 '#links' => $links,
Chris@0 273 ],
Chris@0 274 ];
Chris@0 275
Chris@0 276 $rows[] = $row;
Chris@0 277 }
Chris@0 278 }
Chris@0 279 }
Chris@0 280
Chris@0 281 $build['node_revisions_table'] = [
Chris@0 282 '#theme' => 'table',
Chris@0 283 '#rows' => $rows,
Chris@0 284 '#header' => $header,
Chris@0 285 '#attached' => [
Chris@0 286 'library' => ['node/drupal.node.admin'],
Chris@0 287 ],
Chris@0 288 '#attributes' => ['class' => 'node-revision-table'],
Chris@0 289 ];
Chris@0 290
Chris@0 291 $build['pager'] = ['#type' => 'pager'];
Chris@0 292
Chris@0 293 return $build;
Chris@0 294 }
Chris@0 295
Chris@0 296 /**
Chris@0 297 * The _title_callback for the node.add route.
Chris@0 298 *
Chris@0 299 * @param \Drupal\node\NodeTypeInterface $node_type
Chris@0 300 * The current node.
Chris@0 301 *
Chris@0 302 * @return string
Chris@0 303 * The page title.
Chris@0 304 */
Chris@0 305 public function addPageTitle(NodeTypeInterface $node_type) {
Chris@0 306 return $this->t('Create @name', ['@name' => $node_type->label()]);
Chris@0 307 }
Chris@0 308
Chris@0 309 /**
Chris@0 310 * Gets a list of node revision IDs for a specific node.
Chris@0 311 *
Chris@0 312 * @param \Drupal\node\NodeInterface $node
Chris@0 313 * The node entity.
Chris@0 314 * @param \Drupal\node\NodeStorageInterface $node_storage
Chris@0 315 * The node storage handler.
Chris@0 316 *
Chris@0 317 * @return int[]
Chris@0 318 * Node revision IDs (in descending order).
Chris@0 319 */
Chris@0 320 protected function getRevisionIds(NodeInterface $node, NodeStorageInterface $node_storage) {
Chris@0 321 $result = $node_storage->getQuery()
Chris@0 322 ->allRevisions()
Chris@0 323 ->condition($node->getEntityType()->getKey('id'), $node->id())
Chris@0 324 ->sort($node->getEntityType()->getKey('revision'), 'DESC')
Chris@0 325 ->pager(50)
Chris@0 326 ->execute();
Chris@0 327 return array_keys($result);
Chris@0 328 }
Chris@0 329
Chris@0 330 }