3 namespace Drupal\node\Controller;
5 use Drupal\Component\Utility\Xss;
6 use Drupal\Core\Controller\ControllerBase;
7 use Drupal\Core\Datetime\DateFormatterInterface;
8 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
9 use Drupal\Core\Render\RendererInterface;
11 use Drupal\node\NodeStorageInterface;
12 use Drupal\node\NodeTypeInterface;
13 use Drupal\node\NodeInterface;
14 use Symfony\Component\DependencyInjection\ContainerInterface;
17 * Returns responses for Node routes.
19 class NodeController extends ControllerBase implements ContainerInjectionInterface {
22 * The date formatter service.
24 * @var \Drupal\Core\Datetime\DateFormatterInterface
26 protected $dateFormatter;
29 * The renderer service.
31 * @var \Drupal\Core\Render\RendererInterface
36 * Constructs a NodeController object.
38 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
39 * The date formatter service.
40 * @param \Drupal\Core\Render\RendererInterface $renderer
41 * The renderer service.
43 public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer) {
44 $this->dateFormatter = $date_formatter;
45 $this->renderer = $renderer;
51 public static function create(ContainerInterface $container) {
53 $container->get('date.formatter'),
54 $container->get('renderer')
59 * Displays add content links for available content types.
61 * Redirects to node/add/[type] if only one content type is available.
63 * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
64 * A render array for a list of the node types that can be added; however,
65 * if there is only one node type defined for the site, the function
66 * will return a RedirectResponse to the node add page for that one node
69 public function addPage() {
71 '#theme' => 'node_add_list',
73 'tags' => $this->entityManager()->getDefinition('node_type')->getListCacheTags(),
79 // Only use node types the user has access to.
80 foreach ($this->entityManager()->getStorage('node_type')->loadMultiple() as $type) {
81 $access = $this->entityManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
82 if ($access->isAllowed()) {
83 $content[$type->id()] = $type;
85 $this->renderer->addCacheableDependency($build, $access);
88 // Bypass the node/add listing if only one content type is available.
89 if (count($content) == 1) {
90 $type = array_shift($content);
91 return $this->redirect('node.add', ['node_type' => $type->id()]);
94 $build['#content'] = $content;
100 * Provides the node submission form.
102 * @param \Drupal\node\NodeTypeInterface $node_type
103 * The node type entity for the node.
106 * A node submission form.
108 public function add(NodeTypeInterface $node_type) {
109 $node = $this->entityManager()->getStorage('node')->create([
110 'type' => $node_type->id(),
113 $form = $this->entityFormBuilder()->getForm($node);
119 * Displays a node revision.
121 * @param int $node_revision
122 * The node revision ID.
125 * An array suitable for \Drupal\Core\Render\RendererInterface::render().
127 public function revisionShow($node_revision) {
128 $node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
129 $node = $this->entityManager()->getTranslationFromContext($node);
130 $node_view_controller = new NodeViewController($this->entityManager, $this->renderer, $this->currentUser());
131 $page = $node_view_controller->view($node);
132 unset($page['nodes'][$node->id()]['#cache']);
137 * Page title callback for a node revision.
139 * @param int $node_revision
140 * The node revision ID.
145 public function revisionPageTitle($node_revision) {
146 $node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
147 return $this->t('Revision of %title from %date', ['%title' => $node->label(), '%date' => format_date($node->getRevisionCreationTime())]);
151 * Generates an overview table of older revisions of a node.
153 * @param \Drupal\node\NodeInterface $node
157 * An array as expected by \Drupal\Core\Render\RendererInterface::render().
159 public function revisionOverview(NodeInterface $node) {
160 $account = $this->currentUser();
161 $langcode = $node->language()->getId();
162 $langname = $node->language()->getName();
163 $languages = $node->getTranslationLanguages();
164 $has_translations = (count($languages) > 1);
165 $node_storage = $this->entityManager()->getStorage('node');
166 $type = $node->getType();
168 $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]);
169 $header = [$this->t('Revision'), $this->t('Operations')];
171 $revert_permission = (($account->hasPermission("revert $type revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'));
172 $delete_permission = (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'));
175 $default_revision = $node->getRevisionId();
176 $current_revision_displayed = FALSE;
178 foreach ($this->getRevisionIds($node, $node_storage) as $vid) {
179 /** @var \Drupal\node\NodeInterface $revision */
180 $revision = $node_storage->loadRevision($vid);
181 // Only show revisions that are affected by the language that is being
183 if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
185 '#theme' => 'username',
186 '#account' => $revision->getRevisionUser(),
189 // Use revision link to link to revisions that are not active.
190 $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short');
192 // We treat also the latest translation-affecting revision as current
193 // revision, if it was the default revision, as its values for the
194 // current language will be the same of the current default revision in
196 $is_current_revision = $vid == $default_revision || (!$current_revision_displayed && $revision->wasDefaultRevision());
197 if (!$is_current_revision) {
198 $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid]));
201 $link = $node->link($date);
202 $current_revision_displayed = TRUE;
208 '#type' => 'inline_template',
209 '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}',
212 'username' => $this->renderer->renderPlain($username),
213 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()],
217 // @todo Simplify once https://www.drupal.org/node/2334319 lands.
218 $this->renderer->addCacheableDependency($column['data'], $username);
221 if ($is_current_revision) {
225 '#markup' => $this->t('Current revision'),
226 '#suffix' => '</em>',
232 'class' => ['revision-current'],
237 if ($revert_permission) {
239 'title' => $vid < $node->getRevisionId() ? $this->t('Revert') : $this->t('Set as current revision'),
240 'url' => $has_translations ?
241 Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) :
242 Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
246 if ($delete_permission) {
248 'title' => $this->t('Delete'),
249 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
255 '#type' => 'operations',
265 $build['node_revisions_table'] = [
268 '#header' => $header,
270 'library' => ['node/drupal.node.admin'],
272 '#attributes' => ['class' => 'node-revision-table'],
275 $build['pager'] = ['#type' => 'pager'];
281 * The _title_callback for the node.add route.
283 * @param \Drupal\node\NodeTypeInterface $node_type
289 public function addPageTitle(NodeTypeInterface $node_type) {
290 return $this->t('Create @name', ['@name' => $node_type->label()]);
294 * Gets a list of node revision IDs for a specific node.
296 * @param \Drupal\node\NodeInterface $node
298 * @param \Drupal\node\NodeStorageInterface $node_storage
299 * The node storage handler.
302 * Node revision IDs (in descending order).
304 protected function getRevisionIds(NodeInterface $node, NodeStorageInterface $node_storage) {
305 $result = $node_storage->getQuery()
307 ->condition($node->getEntityType()->getKey('id'), $node->id())
308 ->sort($node->getEntityType()->getKey('revision'), 'DESC')
311 return array_keys($result);