node.module 54.8 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

Dries's avatar
 
Dries committed
3 4
/**
 * @file
5 6 7 8
 * The core module that allows content to be submitted to the site.
 *
 * Modules and scripts may programmatically submit nodes using the usual form
 * API pattern.
Dries's avatar
 
Dries committed
9 10
 */

11
use Drupal\Component\Utility\Environment;
12
use Drupal\Component\Utility\Xss;
13
use Drupal\Core\Access\AccessResult;
14
use Drupal\Core\Cache\Cache;
15 16 17 18
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
19
use Drupal\Core\Form\FormStateInterface;
20
use Drupal\Core\Link;
21
use Drupal\Core\Render\Element;
22
use Drupal\Core\Routing\RouteMatchInterface;
23 24
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
25
use Drupal\Core\Url;
26
use Drupal\field\Entity\FieldConfig;
27 28
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\ConfigurableLanguageInterface;
29 30
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
31
use Drupal\node\NodeInterface;
32
use Drupal\node\NodeTypeInterface;
33
use Drupal\user\UserInterface;
34

35
/**
36
 * Denotes that the node is not published.
37
 *
38
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
39
 *   Use \Drupal\node\NodeInterface::NOT_PUBLISHED instead.
40 41
 *
 * @see https://www.drupal.org/node/2316145
42
 */
43
const NODE_NOT_PUBLISHED = 0;
44 45

/**
46
 * Denotes that the node is published.
47
 *
48
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
49
 *   Use \Drupal\node\NodeInterface::PUBLISHED instead.
50 51
 *
 * @see https://www.drupal.org/node/2316145
52
 */
53
const NODE_PUBLISHED = 1;
54 55

/**
56
 * Denotes that the node is not promoted to the front page.
57
 *
58
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
59
 *   Use \Drupal\node\NodeInterface::NOT_PROMOTED instead.
60 61
 *
 * @see https://www.drupal.org/node/2316145
62
 */
63
const NODE_NOT_PROMOTED = 0;
64 65

/**
66
 * Denotes that the node is promoted to the front page.
67
 *
68
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
69
 *   Use \Drupal\node\NodeInterface::PROMOTED instead.
70 71
 *
 * @see https://www.drupal.org/node/2316145
72
 */
73
const NODE_PROMOTED = 1;
74 75

/**
76
 * Denotes that the node is not sticky at the top of the page.
77
 *
78
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
79
 *   Use \Drupal\node\NodeInterface::NOT_STICKY instead.
80 81
 *
 * @see https://www.drupal.org/node/2316145
82
 */
83
const NODE_NOT_STICKY = 0;
84 85

/**
86
 * Denotes that the node is sticky at the top of the page.
87
 *
88
 * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
89
 *   Use \Drupal\node\NodeInterface::STICKY instead.
90 91
 *
 * @see https://www.drupal.org/node/2316145
92
 */
93
const NODE_STICKY = 1;
94

Dries's avatar
 
Dries committed
95
/**
96
 * Implements hook_help().
Dries's avatar
 
Dries committed
97
 */
98
function node_help($route_name, RouteMatchInterface $route_match) {
99 100 101
  // Remind site administrators about the {node_access} table being flagged
  // for rebuild. We don't need to issue the message on the confirm form, or
  // while the rebuild is being processed.
102
  if ($route_name != 'node.configure_rebuild_confirm' && $route_name != 'system.batch_page.normal' && $route_name != 'help.page.node' && $route_name != 'help.main'
103
    && \Drupal::currentUser()->hasPermission('access administration pages') && node_access_needs_rebuild()) {
104
    if ($route_name == 'system.status') {
105 106 107
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
108
      $message = t('The content access permissions need to be rebuilt. <a href=":node_access_rebuild">Rebuild permissions</a>.', [':node_access_rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString()]);
109
    }
110
    \Drupal::messenger()->addError($message);
111 112
  }

113 114
  switch ($route_name) {
    case 'help.page.node':
115 116
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
117
      $output .= '<p>' . t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href=":field">Field module</a>). For more information, see the <a href=":node">online documentation for the Node module</a>.', [':node' => 'https://www.drupal.org/documentation/modules/node', ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>';
118 119
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
120
      $output .= '<dt>' . t('Creating content') . '</dt>';
121
      $output .= '<dd>' . t('When new content is created, the Node module records basic information about the content, including the author, date of creation, and the <a href=":content-type">Content type</a>. It also manages the <em>publishing options</em>, which define whether or not the content is published, promoted to the front page of the site, and/or sticky at the top of content lists. Default settings can be configured for each <a href=":content-type">type of content</a> on your site.', [':content-type' => Url::fromRoute('entity.node_type.collection')->toString()]) . '</dd>';
122
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
123
      $output .= '<dd>' . t('The Node module gives users with the <em>Administer content types</em> permission the ability to <a href=":content-new">create new content types</a> in addition to the default ones already configured. Creating custom content types gives you the flexibility to add <a href=":field">fields</a> and configure default settings that suit the differing needs of various site content.', [':content-new' => Url::fromRoute('node.type_add')->toString(), ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</dd>';
124
      $output .= '<dt>' . t('Administering content') . '</dt>';
125
      $output .= '<dd>' . t('The <a href=":content">Content</a> page lists your content, allowing you add new content, filter, edit or delete existing content, or perform bulk operations on existing content.', [':content' => Url::fromRoute('system.admin_content')->toString()]) . '</dd>';
126 127
      $output .= '<dt>' . t('Creating revisions') . '</dt>';
      $output .= '<dd>' . t('The Node module also enables you to create multiple versions of any content, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
128
      $output .= '<dt>' . t('User permissions') . '</dt>';
129
      $output .= '<dd>' . t('The Node module makes a number of permissions available for each content type, which can be set by role on the <a href=":permissions">permissions page</a>.', [':permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-node'])->toString()]) . '</dd>';
130
      $output .= '</dl>';
131
      return $output;
132

133
    case 'node.type_add':
134
      return '<p>' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
135

136 137
    case 'entity.entity_form_display.node.default':
    case 'entity.entity_form_display.node.form_mode':
138
      $type = $route_match->getParameter('node_type');
139
      return '<p>' . t('Content items can be edited using different form modes. Here, you can define which fields are shown and hidden when %type content is edited in each form mode, and define how the field form widgets are displayed in each form mode.', ['%type' => $type->label()]) . '</p>';
140

141 142
    case 'entity.entity_view_display.node.default':
    case 'entity.entity_view_display.node.view_mode':
143
      $type = $route_match->getParameter('node_type');
144
      return '<p>' . t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p>' .
145
        '<p>' . t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', ['%type' => $type->label()]) . '</p>';
146

147
    case 'entity.node.version_history':
148
      return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert to older versions.') . '</p>';
149

150
    case 'entity.node.edit_form':
151
      $node = $route_match->getParameter('node');
152
      $type = NodeType::load($node->getType());
153 154
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
Dries's avatar
 
Dries committed
155

156
    case 'node.add':
157
      $type = $route_match->getParameter('node_type');
158 159
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
160
  }
Dries's avatar
 
Dries committed
161 162
}

163
/**
164
 * Implements hook_theme().
165 166
 */
function node_theme() {
167 168
  return [
    'node' => [
169
      'render element' => 'elements',
170 171 172 173 174
    ],
    'node_add_list' => [
      'variables' => ['content' => NULL],
    ],
    'node_edit_form' => [
175
      'render element' => 'form',
176 177
    ],
    'field__node__title' => [
178
      'base hook' => 'field',
179 180
    ],
    'field__node__uid' => [
181
      'base hook' => 'field',
182 183
    ],
    'field__node__created' => [
184
      'base hook' => 'field',
185 186
    ],
  ];
187 188
}

189
/**
190
 * Implements hook_entity_view_display_alter().
191
 */
192
function node_entity_view_display_alter(EntityViewDisplayInterface $display, $context) {
193 194 195 196 197 198 199 200
  if ($context['entity_type'] == 'node') {
    // Hide field labels in search index.
    if ($context['view_mode'] == 'search_index') {
      foreach ($display->getComponents() as $name => $options) {
        if (isset($options['label'])) {
          $options['label'] = 'hidden';
          $display->setComponent($name, $options);
        }
201 202
      }
    }
203 204 205
  }
}

Dries's avatar
 
Dries committed
206
/**
207
 * Gathers a listing of links to nodes.
Dries's avatar
 
Dries committed
208
 *
209
 * @param \Drupal\Core\Database\StatementInterface $result
210
 *   A database result object from a query to fetch node entities. If your
211
 *   query joins the {comment_entity_statistics} table so that the comment_count
212 213
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
 
Dries committed
214
 * @param $title
215
 *   (optional) A heading for the resulting list.
Dries's avatar
 
Dries committed
216
 *
217
 * @return array|false
218 219
 *   A renderable array containing a list of linked node titles fetched from
 *   $result, or FALSE if there are no rows in $result.
Dries's avatar
 
Dries committed
220
 */
221
function node_title_list(StatementInterface $result, $title = NULL) {
222
  $items = [];
223
  $num_rows = FALSE;
224
  $nids = [];
225
  foreach ($result as $row) {
226
    // Do not use $node->label() or $node->toUrl() here, because we only have
227
    // database rows, not actual nodes.
228
    $nids[] = $row->nid;
229
    $options = !empty($row->comment_count) ? ['attributes' => ['title' => \Drupal::translation()->formatPlural($row->comment_count, '1 comment', '@count comments')]] : [];
230
    $items[] = Link::fromTextAndUrl($row->title, Url::fromRoute('entity.node.canonical', ['node' => $row->nid], $options))->toString();
231
    $num_rows = TRUE;
Dries's avatar
 
Dries committed
232 233
  }

234
  return $num_rows ? ['#theme' => 'item_list__node', '#items' => $items, '#title' => $title, '#cache' => ['tags' => Cache::mergeTags(['node_list'], Cache::buildTags('node', $nids))]] : FALSE;
Dries's avatar
 
Dries committed
235 236
}

Dries's avatar
 
Dries committed
237
/**
238
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
239
 *
240
 * @param int $nid
Dries's avatar
 
Dries committed
241
 *   Node ID whose history supplies the "last viewed" timestamp.
242
 * @param int $timestamp
Dries's avatar
 
Dries committed
243
 *   Time which is compared against node's "last viewed" timestamp.
244
 *
245
 * @return int
246
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
247
 */
248
function node_mark($nid, $timestamp) {
249

250
  $cache = &drupal_static(__FUNCTION__, []);
Dries's avatar
 
Dries committed
251

252
  if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
253 254
    return MARK_READ;
  }
Dries's avatar
Dries committed
255
  if (!isset($cache[$nid])) {
256
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
257
  }
258
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
259 260
    return MARK_NEW;
  }
261
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
262 263 264
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
265 266
}

267 268 269
/**
 * Returns a list of all the available node types.
 *
270 271
 * This list can include types that are queued for addition or deletion.
 *
272
 * @return \Drupal\node\NodeTypeInterface[]
273
 *   An array of node type entities, keyed by ID.
274
 *
275
 * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0.
276 277
 *   Use \Drupal\node\Entity\NodeType::loadMultiple().
 *
278
 * @see \Drupal\node\Entity\NodeType::load()
279 280
 */
function node_type_get_types() {
281
  return NodeType::loadMultiple();
282 283 284
}

/**
285 286 287
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
288
 *
289
 * @return string[]
290
 *   An array of node type labels, keyed by the node type name.
291 292
 */
function node_type_get_names() {
293 294
  return array_map(function ($bundle_info) {
    return $bundle_info['label'];
295
  }, \Drupal::service('entity_type.bundle.info')->getBundleInfo('node'));
296 297 298 299 300
}

/**
 * Returns the node type label for the passed node.
 *
301
 * @param \Drupal\node\NodeInterface $node
302 303 304 305
 *   A node entity to return the node type's label for.
 *
 * @return string|false
 *   The node type label or FALSE if the node type is not found.
306
 *
307 308
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
309
 */
310
function node_get_type_label(NodeInterface $node) {
311
  $type = NodeType::load($node->bundle());
312
  return $type ? $type->label() : FALSE;
313 314 315 316 317
}

/**
 * Description callback: Returns the node type description.
 *
318
 * @param \Drupal\node\NodeTypeInterface $node_type
319 320
 *   The node type object.
 *
321
 * @return string
322 323
 *   The node type description.
 */
324
function node_type_get_description(NodeTypeInterface $node_type) {
325
  return $node_type->getDescription();
326 327
}

328
/**
329
 * Menu argument loader: Loads a node type by string.
330 331
 *
 * @param $name
332
 *   The machine name of a node type to load.
333
 *
334
 * @return \Drupal\node\NodeTypeInterface
335
 *   A node type object or NULL if $name does not exist.
336
 *
337
 * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use
338 339 340
 *   \Drupal\node\Entity\NodeType::load().
 *
 * @see https://www.drupal.org/node/2266845
341 342
 */
function node_type_load($name) {
343
  @trigger_error('node_type_load() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\node\Entity\NodeType::load(). See https://www.drupal.org/node/2266845', E_USER_DEPRECATED);
344
  return NodeType::load($name);
345
}
346

347
/**
348
 * Adds the default body field to a node type.
349
 *
350
 * @param \Drupal\node\NodeTypeInterface $type
351
 *   A node type object.
352
 * @param string $label
353
 *   (optional) The label for the body instance.
354
 *
355 356
 * @return \Drupal\field\Entity\FieldConfig
 *   A Body field object.
357
 */
358
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
359
  // Add or remove the body field, as needed.
360
  $field_storage = FieldStorageConfig::loadByName('node', 'body');
361 362
  $field = FieldConfig::loadByName('node', $type->id(), 'body');
  if (empty($field)) {
363
    $field = FieldConfig::create([
364
      'field_storage' => $field_storage,
365
      'bundle' => $type->id(),
366
      'label' => $label,
367
      'settings' => ['display_summary' => TRUE],
368
    ]);
369
    $field->save();
370

371 372 373 374 375
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');

    // Assign widget settings for the default form mode.
    $display_repository->getFormDisplay('node', $type->id())
376
      ->setComponent('body', [
377
        'type' => 'text_textarea_with_summary',
378
      ])
379 380
      ->save();

381
    // Assign display settings for the 'default' and 'teaser' view modes.
382
    $display_repository->getViewDisplay('node', $type->id())
383
      ->setComponent('body', [
384 385
        'label' => 'hidden',
        'type' => 'text_default',
386
      ])
387
      ->save();
388 389 390

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
391
    $view_modes = \Drupal::service('entity_display.repository')->getViewModes('node');
392
    if (isset($view_modes['teaser'])) {
393
      $display_repository->getViewDisplay('node', $type->id(), 'teaser')
394
        ->setComponent('body', [
395 396
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
397
        ])
398 399
        ->save();
    }
400
  }
401

402
  return $field;
403
}
404

405
/**
406
 * Implements hook_entity_extra_field_info().
407
 */
408
function node_entity_extra_field_info() {
409
  $extra = [];
410
  $description = t('Node module element');
411
  foreach (NodeType::loadMultiple() as $bundle) {
412
    $extra['node'][$bundle->id()]['display']['links'] = [
413 414 415 416
      'label' => t('Links'),
      'description' => $description,
      'weight' => 100,
      'visible' => TRUE,
417
    ];
418
  }
419 420

  return $extra;
421 422
}

423
/**
424 425
 * Updates all nodes of one type to be of another type.
 *
426
 * @param string $old_id
427
 *   The current node type of the nodes.
428
 * @param string $new_id
429
 *   The new node type of the nodes.
430 431
 *
 * @return
432
 *   The number of nodes whose node type field was modified.
433
 */
434
function node_type_update_nodes($old_id, $new_id) {
435
  return \Drupal::entityTypeManager()->getStorage('node')->updateType($old_id, $new_id);
Dries's avatar
 
Dries committed
436
}
Dries's avatar
 
Dries committed
437

438
/**
439
 * Loads node entities from the database.
440 441
 *
 * This function should be used whenever you need to load more than one node
442 443
 * from the database. Nodes are loaded into memory and will not require database
 * access if loaded again during the same page request.
Dries's avatar
 
Dries committed
444
 *
445 446
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
447
 * @param bool $reset
448 449
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries's avatar
 
Dries committed
450
 *
451
 * @return \Drupal\node\NodeInterface[]
452
 *   An array of node entities indexed by nid.
453
 *
454
 * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use
455
 *   \Drupal\node\Entity\Node::loadMultiple().
456
 *
457
 * @see https://www.drupal.org/node/2266845
Dries's avatar
 
Dries committed
458
 */
459
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
460
  @trigger_error('node_load_multiple() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\node\Entity\Node::loadMultiple(). See https://www.drupal.org/node/2266845', E_USER_DEPRECATED);
461
  if ($reset) {
462
    \Drupal::entityTypeManager()->getStorage('node')->resetCache($nids);
463 464
  }
  return Node::loadMultiple($nids);
465 466 467
}

/**
468
 * Loads a node entity from the database.
469
 *
470
 * @param int $nid
471
 *   The node ID.
472
 * @param bool $reset
473 474
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
475
 *
476 477
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
478
 *
479
 * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use
480 481 482
 *   \Drupal\node\Entity\Node::load().
 *
 * @see https://www.drupal.org/node/2266845
483
 */
484
function node_load($nid = NULL, $reset = FALSE) {
485
  @trigger_error('node_load() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\node\Entity\Node::load(). See https://www.drupal.org/node/2266845', E_USER_DEPRECATED);
486
  if ($reset) {
487
    \Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]);
488 489
  }
  return Node::load($nid);
490 491 492 493 494
}

/**
 * Loads a node revision from the database.
 *
495
 * @param int $vid
496 497
 *   The node revision id.
 *
498 499
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
500 501
 */
function node_revision_load($vid = NULL) {
502
  return \Drupal::entityTypeManager()->getStorage('node')->loadRevision($vid);
Dries's avatar
 
Dries committed
503 504
}

505
/**
506
 * Deletes a node revision.
507
 *
508
 * @param int $revision_id
509 510 511
 *   The revision ID to delete.
 */
function node_revision_delete($revision_id) {
512
  \Drupal::entityTypeManager()->getStorage('node')->deleteRevision($revision_id);
513 514
}

515
/**
516
 * Checks whether the current page is the full page view of the passed-in node.
517
 *
518
 * @param \Drupal\node\NodeInterface $node
519
 *   A node entity.
520
 *
521
 * @return int|false
522
 *   The ID of the node if this is a full page view, otherwise FALSE.
523
 */
524
function node_is_page(NodeInterface $node) {
525
  $route_match = \Drupal::routeMatch();
526
  if ($route_match->getRouteName() == 'entity.node.canonical') {
527
    $page_node = $route_match->getParameter('node');
528
  }
529
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
530 531
}

532 533 534 535 536 537 538 539 540
/**
 * Prepares variables for list of available node type templates.
 *
 * Default template: node-add-list.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - content: An array of content types.
 *
541
 * @see \Drupal\node\Controller\NodeController::addPage()
542 543
 */
function template_preprocess_node_add_list(&$variables) {
544
  $variables['types'] = [];
545 546
  if (!empty($variables['content'])) {
    foreach ($variables['content'] as $type) {
547
      $variables['types'][$type->id()] = [
548
        'type' => $type->id(),
549
        'add_link' => Link::fromTextAndUrl($type->label(), Url::fromRoute('node.add', ['node_type' => $type->id()]))->toString(),
550
        'description' => [
551
          '#markup' => $type->getDescription(),
552 553
        ],
      ];
554 555 556 557
    }
  }
}

558 559 560 561
/**
 * Implements hook_preprocess_HOOK() for HTML document templates.
 */
function node_preprocess_html(&$variables) {
562 563 564 565 566 567
  // If on an individual node page or node preview page, add the node type to
  // the body classes.
  if (($node = \Drupal::routeMatch()->getParameter('node')) || ($node = \Drupal::routeMatch()->getParameter('node_preview'))) {
    if ($node instanceof NodeInterface) {
      $variables['node_type'] = $node->getType();
    }
568 569 570
  }
}

571
/**
572
 * Implements hook_preprocess_HOOK() for block templates.
573 574
 */
function node_preprocess_block(&$variables) {
575
  if ($variables['configuration']['provider'] == 'node') {
576
    switch ($variables['elements']['#plugin_id']) {
577
      case 'node_syndicate_block':
578
        $variables['attributes']['role'] = 'complementary';
579 580 581 582 583
        break;
    }
  }
}

584 585 586 587
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
588
  $suggestions = [];
589
  $node = $variables['elements']['#node'];
590
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
591

592
  $suggestions[] = 'node__' . $sanitized_view_mode;
593
  $suggestions[] = 'node__' . $node->bundle();
594
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
595
  $suggestions[] = 'node__' . $node->id();
596
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
597 598 599 600

  return $suggestions;
}

601
/**
602
 * Prepares variables for node templates.
603
 *
604
 * Default template: node.html.twig.
605
 *
606 607 608
 * Most themes use their own copy of node.html.twig. The default is located
 * inside "/core/modules/node/templates/node.html.twig". Look in there for the
 * full list of variables.
609
 *
610 611 612 613 614 615 616 617 618
 * By default this function performs special preprocessing of some base fields
 * so they are available as variables in the template. For example 'title'
 * appears as 'label'. 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().
 *
619
 * @param array $variables
620 621 622
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - node: The node object.
623
 *   - view_mode: View mode; e.g., 'full', 'teaser', etc.
624 625 626
 *
 * @see hook_entity_type_build()
 * @see \Drupal\Core\Field\BaseFieldDefinition::setDisplayConfigurable()
627 628
 */
function template_preprocess_node(&$variables) {
629
  $variables['view_mode'] = $variables['elements']['#view_mode'];
630
  // Provide a distinct $teaser boolean.
631
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
632
  $variables['node'] = $variables['elements']['#node'];
633
  /** @var \Drupal\node\NodeInterface $node */
634
  $node = $variables['node'];
635 636 637 638 639 640
  $skip_custom_preprocessing = $node->getEntityType()->get('enable_base_field_custom_preprocess_skipping');

  // Make created, uid and title 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
641
  //   Eventually delete this code and matching template lines. Using
642 643 644 645 646 647 648 649 650
  //   $variables['content'] is more flexible and consistent.
  $submitted_configurable = $node->getFieldDefinition('created')->isDisplayConfigurable('view') || $node->getFieldDefinition('uid')->isDisplayConfigurable('view');
  if (!$skip_custom_preprocessing || !$submitted_configurable) {
    $variables['date'] = \Drupal::service('renderer')->render($variables['elements']['created']);
    unset($variables['elements']['created']);
    $variables['author_name'] = \Drupal::service('renderer')->render($variables['elements']['uid']);
    unset($variables['elements']['uid']);
  }

651
  if (isset($variables['elements']['title']) && (!$skip_custom_preprocessing || !$node->getFieldDefinition('title')->isDisplayConfigurable('view'))) {
652 653 654
    $variables['label'] = $variables['elements']['title'];