node.module 50.5 KB
Newer Older
1 2
<?php

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.
9 10
 */

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

32
/**
33
 * Denotes that the node is not published.
34 35 36
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::NOT_PUBLISHED instead.
37 38
 *
 * @see https://www.drupal.org/node/2316145
39
 */
40
const NODE_NOT_PUBLISHED = 0;
41 42

/**
43
 * Denotes that the node is published.
44 45 46
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::PUBLISHED instead.
47 48
 *
 * @see https://www.drupal.org/node/2316145
49
 */
50
const NODE_PUBLISHED = 1;
51 52

/**
53
 * Denotes that the node is not promoted to the front page.
54 55 56
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::NOT_PROMOTED instead.
57 58
 *
 * @see https://www.drupal.org/node/2316145
59
 */
60
const NODE_NOT_PROMOTED = 0;
61 62

/**
63
 * Denotes that the node is promoted to the front page.
64 65 66
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::PROMOTED instead.
67 68
 *
 * @see https://www.drupal.org/node/2316145
69
 */
70
const NODE_PROMOTED = 1;
71 72

/**
73
 * Denotes that the node is not sticky at the top of the page.
74 75 76
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::NOT_STICKY instead.
77 78
 *
 * @see https://www.drupal.org/node/2316145
79
 */
80
const NODE_NOT_STICKY = 0;
81 82

/**
83
 * Denotes that the node is sticky at the top of the page.
84 85 86
 *
 * @deprecated Scheduled for removal in Drupal 9.0.x.
 *   Use \Drupal\node\NodeInterface::STICKY instead.
87 88
 *
 * @see https://www.drupal.org/node/2316145
89
 */
90
const NODE_STICKY = 1;
91

92
/**
93
 * Implements hook_help().
94
 */
95
function node_help($route_name, RouteMatchInterface $route_match) {
96 97 98
  // 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.
99
  if ($route_name != 'node.configure_rebuild_confirm' && $route_name != 'system.batch_page.normal' && $route_name != 'help.page.node' && $route_name != 'help.main'
100
    && \Drupal::currentUser()->hasPermission('access administration pages') && node_access_needs_rebuild()) {
101
    if ($route_name == 'system.status') {
102 103 104
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
105
      $message = t('The content access permissions need to be rebuilt. <a href=":node_access_rebuild">Rebuild permissions</a>.', [':node_access_rebuild' => \Drupal::url('node.configure_rebuild_confirm')]);
106 107 108 109
    }
    drupal_set_message($message, 'error');
  }

110 111
  switch ($route_name) {
    case 'help.page.node':
112 113
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
114
      $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' => \Drupal::url('help.page', ['name' => 'field'])]) . '</p>';
115 116
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
117
      $output .= '<dt>' . t('Creating content') . '</dt>';
118
      $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' => \Drupal::url('entity.node_type.collection')]) . '</dd>';
119
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
120
      $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' => \Drupal::url('node.type_add'), ':field' => \Drupal::url('help.page', ['name' => 'field'])]) . '</dd>';
121
      $output .= '<dt>' . t('Administering content') . '</dt>';
122
      $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' => \Drupal::url('system.admin_content')]) . '</dd>';
123 124
      $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>';
125
      $output .= '<dt>' . t('User permissions') . '</dt>';
126
      $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' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-node'])]) . '</dd>';
127
      $output .= '</dl>';
128
      return $output;
129

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

133 134
    case 'entity.entity_form_display.node.default':
    case 'entity.entity_form_display.node.form_mode':
135
      $type = $route_match->getParameter('node_type');
136
      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>';
137

138 139
    case 'entity.entity_view_display.node.default':
    case 'entity.entity_view_display.node.view_mode':
140
      $type = $route_match->getParameter('node_type');
141
      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>' .
142
        '<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>';
143

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

147
    case 'entity.node.edit_form':
148
      $node = $route_match->getParameter('node');
149
      $type = NodeType::load($node->getType());
150 151
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
152

153
    case 'node.add':
154
      $type = $route_match->getParameter('node_type');
155 156
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
157
  }
158 159
}

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

186
/**
187
 * Implements hook_entity_view_display_alter().
188
 */
189
function node_entity_view_display_alter(EntityViewDisplayInterface $display, $context) {
190 191 192 193 194 195 196 197
  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);
        }
198 199
      }
    }
200 201 202
  }
}

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

231
  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
232 233
}

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

247
  $cache = &drupal_static(__FUNCTION__, []);
248

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

264 265 266
/**
 * Returns a list of all the available node types.
 *
267 268
 * This list can include types that are queued for addition or deletion.
 *
269
 * @return \Drupal\node\NodeTypeInterface[]
270
 *   An array of node type entities, keyed by ID.
271
 *
272 273 274
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::loadMultiple().
 *
275
 * @see \Drupal\node\Entity\NodeType::load()
276 277
 */
function node_type_get_types() {
278
  return NodeType::loadMultiple();
279 280 281
}

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

/**
 * Returns the node type label for the passed node.
 *
298
 * @param \Drupal\node\NodeInterface $node
299 300 301 302
 *   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.
303
 *
304 305
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
306
 */
307
function node_get_type_label(NodeInterface $node) {
308
  $type = NodeType::load($node->bundle());
309
  return $type ? $type->label() : FALSE;
310 311 312 313 314
}

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

325
/**
326
 * Menu argument loader: Loads a node type by string.
327 328
 *
 * @param $name
329
 *   The machine name of a node type to load.
330
 *
331
 * @return \Drupal\node\NodeTypeInterface
332
 *   A node type object or NULL if $name does not exist.
333 334 335
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::load().
336 337
 */
function node_type_load($name) {
338
  return NodeType::load($name);
339
}
340

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

365
    // Assign widget settings for the 'default' form mode.
366
    entity_get_form_display('node', $type->id(), 'default')
367
      ->setComponent('body', [
368
        'type' => 'text_textarea_with_summary',
369
      ])
370 371
      ->save();

372
    // Assign display settings for the 'default' and 'teaser' view modes.
373
    entity_get_display('node', $type->id(), 'default')
374
      ->setComponent('body', [
375 376
        'label' => 'hidden',
        'type' => 'text_default',
377
      ])
378
      ->save();
379 380 381

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
382
    $view_modes = \Drupal::entityManager()->getViewModes('node');
383
    if (isset($view_modes['teaser'])) {
384
      entity_get_display('node', $type->id(), 'teaser')
385
        ->setComponent('body', [
386 387
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
388
        ])
389 390
        ->save();
    }
391
  }
392

393
  return $field;
394
}
395

396
/**
397
 * Implements hook_entity_extra_field_info().
398
 */
399
function node_entity_extra_field_info() {
400
  $extra = [];
401
  $description = t('Node module element');
402
  foreach (NodeType::loadMultiple() as $bundle) {
403
    $extra['node'][$bundle->id()]['display']['links'] = [
404 405 406 407
      'label' => t('Links'),
      'description' => $description,
      'weight' => 100,
      'visible' => TRUE,
408
    ];
409
  }
410 411

  return $extra;
412 413
}

414
/**
415 416
 * Updates all nodes of one type to be of another type.
 *
417
 * @param string $old_id
418
 *   The current node type of the nodes.
419
 * @param string $new_id
420
 *   The new node type of the nodes.
421 422
 *
 * @return
423
 *   The number of nodes whose node type field was modified.
424
 */
425
function node_type_update_nodes($old_id, $new_id) {
426
  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
427
}
428

429
/**
430
 * Loads node entities from the database.
431 432
 *
 * This function should be used whenever you need to load more than one node
433 434
 * from the database. Nodes are loaded into memory and will not require database
 * access if loaded again during the same page request.
435
 *
436 437
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
438
 * @param bool $reset
439 440
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
441
 *
442
 * @return \Drupal\node\NodeInterface[]
443
 *   An array of node entities indexed by nid.
444
 *
445 446 447
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::loadMultiple().
 *
448
 * @see entity_load_multiple()
449
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
450
 */
451
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
452 453 454 455
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache($nids);
  }
  return Node::loadMultiple($nids);
456 457 458
}

/**
459
 * Loads a node entity from the database.
460
 *
461
 * @param int $nid
462
 *   The node ID.
463
 * @param bool $reset
464 465
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
466
 *
467 468
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
469 470 471
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::load().
472
 */
473
function node_load($nid = NULL, $reset = FALSE) {
474
  if ($reset) {
475
    \Drupal::entityManager()->getStorage('node')->resetCache([$nid]);
476 477
  }
  return Node::load($nid);
478 479 480 481 482
}

/**
 * Loads a node revision from the database.
 *
483
 * @param int $vid
484 485
 *   The node revision id.
 *
486 487
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
488 489
 */
function node_revision_load($vid = NULL) {
490
  return \Drupal::entityTypeManager()->getStorage('node')->loadRevision($vid);
491 492
}

493
/**
494
 * Deletes a node revision.
495
 *
496
 * @param int $revision_id
497 498 499
 *   The revision ID to delete.
 */
function node_revision_delete($revision_id) {
500
  \Drupal::entityTypeManager()->getStorage('node')->deleteRevision($revision_id);
501 502
}

503
/**
504
 * Checks whether the current page is the full page view of the passed-in node.
505
 *
506
 * @param \Drupal\node\NodeInterface $node
507
 *   A node entity.
508
 *
509
 * @return int|false
510
 *   The ID of the node if this is a full page view, otherwise FALSE.
511
 */
512
function node_is_page(NodeInterface $node) {
513
  $route_match = \Drupal::routeMatch();
514
  if ($route_match->getRouteName() == 'entity.node.canonical') {
515
    $page_node = $route_match->getParameter('node');
516
  }
517
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
518 519
}

520 521 522 523 524 525 526 527 528 529 530 531
/**
 * 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.
 *
 * @see node_add_page()
 */
function template_preprocess_node_add_list(&$variables) {
532
  $variables['types'] = [];
533 534
  if (!empty($variables['content'])) {
    foreach ($variables['content'] as $type) {
535
      $variables['types'][$type->id()] = [
536
        'type' => $type->id(),
537 538
        'add_link' => \Drupal::l($type->label(), new Url('node.add', ['node_type' => $type->id()])),
        'description' => [
539
          '#markup' => $type->getDescription(),
540 541
        ],
      ];
542 543 544 545
    }
  }
}

546 547 548 549 550
/**
 * Implements hook_preprocess_HOOK() for HTML document templates.
 */
function node_preprocess_html(&$variables) {
  // If on an individual node page, add the node type to body classes.
551
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
552
    $variables['node_type'] = $node->getType();
553 554 555
  }
}

556
/**
557
 * Implements hook_preprocess_HOOK() for block templates.
558 559
 */
function node_preprocess_block(&$variables) {
560
  if ($variables['configuration']['provider'] == 'node') {
561
    switch ($variables['elements']['#plugin_id']) {
562
      case 'node_syndicate_block':
563
        $variables['attributes']['role'] = 'complementary';
564 565 566 567 568
        break;
    }
  }
}

569 570 571 572
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
573
  $suggestions = [];
574
  $node = $variables['elements']['#node'];
575
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
576

577
  $suggestions[] = 'node__' . $sanitized_view_mode;
578
  $suggestions[] = 'node__' . $node->bundle();
579
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
580
  $suggestions[] = 'node__' . $node->id();
581
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
582 583 584 585

  return $suggestions;
}

586
/**
587
 * Prepares variables for node templates.
588
 *
589
 * Default template: node.html.twig.
590
 *
591 592 593
 * 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.
594 595
 *
 * @param array $variables
596 597 598
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - node: The node object.
599
 *   - view_mode: View mode; e.g., 'full', 'teaser', etc.
600 601
 */
function template_preprocess_node(&$variables) {
602
  $variables['view_mode'] = $variables['elements']['#view_mode'];
603
  // Provide a distinct $teaser boolean.
604
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
605
  $variables['node'] = $variables['elements']['#node'];
606
  /** @var \Drupal\node\NodeInterface $node */
607
  $node = $variables['node'];
608
  $variables['date'] = \Drupal::service('renderer')->render($variables['elements']['created']);
609
  unset($variables['elements']['created']);
610
  $variables['author_name'] = \Drupal::service('renderer')->render($variables['elements']['uid']);
611
  unset($variables['elements']['uid']);
612

613
  $variables['url'] = $node->url('canonical', [
614
    'language' => $node->language(),
615
  ]);
616 617
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
618 619 620
  // The 'page' variable is set to TRUE in two occasions:
  //   - The view mode is 'full' and we are on the 'node.view' route.
  //   - The node is in preview and view mode is either 'full' or 'default'.
621
  $variables['page'] = ($variables['view_mode'] == 'full' && (node_is_page($node)) || (isset($node->in_preview) && in_array($node->preview_view_mode, ['full', 'default'])));
622

623
  // Helpful $content variable for templates.
624
  $variables += ['content' => []];
625
  foreach (Element::children($variables['elements']) as $key) {
626 627
    $variables['content'][$key] = $variables['elements'][$key];
  }
628 629

  // Display post information only on certain node types.
630
  $node_type = $node->type->entity;
631 632
  // Used by RDF to add attributes around the author and date submitted.
  $variables['author_attributes'] = new Attribute();
633
  $variables['display_submitted'] = $node_type->displaySubmitted();
634
  if ($variables['display_submitted']) {
635
    if (theme_get_setting('features.node_user_picture')) {
636
      // To change user picture settings (e.g. image style), edit the 'compact'
637 638
      // view mode on the User entity. Note that the 'compact' view mode might
      // not be configured, so remember to always check the theme setting first.
639
      $variables['author_picture'] = user_view($node->getOwner(), 'compact');
640
    }
641
  }
642

643
  // Add article ARIA role.
644
  $variables['attributes']['role'] = 'article';
645 646
}

647 648 649 650 651 652 653
/**
 * Implements hook_cron().
 */
function node_cron() {
  // Calculate the oldest and newest node created times, for use in search
  // rankings. (Note that field aliases have to be variables passed by
  // reference.)
654 655 656 657 658 659 660 661 662
  if (\Drupal::moduleHandler()->moduleExists('search')) {
    $min_alias = 'min_created';
    $max_alias = 'max_created';
    $result = \Drupal::entityQueryAggregate('node')
      ->aggregate('created', 'MIN', NULL, $min_alias)
      ->aggregate('created', 'MAX', NULL, $max_alias)
      ->execute();
    if (isset($result[0])) {
      // Make an array with definite keys and store it in the state system.
663
      $array = [
664 665
        'min_created' => $result[0][$min_alias],
        'max_created' => $result[0][$max_alias],
666
      ];
667 668
      \Drupal::state()->set('node.min_max_update_time', $array);
    }
669 670 671
  }
}

672
/**
673
 * Implements hook_ranking().
674 675 676
 */
function node_ranking() {
  // Create the ranking array and add the basic ranking options.
677 678
  $ranking = [
    'relevance' => [
679 680 681
      'title' => t('Keyword relevance'),
      // Average relevance values hover around 0.15
      'score' => 'i.relevance',
682 683
    ],
    'sticky' => [
684 685 686
      'title' => t('Content is sticky at top of lists'),
      // The sticky flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.sticky',
687 688
    ],
    'promote' => [
689 690 691
      'title' => t('Content is promoted to the front page'),
      // The promote flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.promote',
692 693
    ],
  ];
694 695 696
  // Add relevance based on updated date, but only if it the scale values have
  // been calculated in node_cron().
  if ($node_min_max = \Drupal::state()->get('node.min_max_update_time')) {
697
    $ranking['recent'] = [
698 699
      'title' => t('Recently created'),
      // Exponential decay with half life of 14% of the age range of nodes.
700
      'score' => 'EXP(-5 * (1 - (n.created - :node_oldest) / :node_range))',
701
      'arguments' => [
702 703
        ':node_oldest' => $node_min_max['min_created'],
        ':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
704 705
      ],
    ];
706 707 708 709
  }
  return $ranking;
}

710
/**
711
 * Implements hook_user_cancel().
712
 */
713
function node_user_cancel($edit, $account, $method) {
714 715 716
  switch ($method) {
    case 'user_cancel_block_unpublish':
      // Unpublish nodes (current revisions).
717
      $nids = \Drupal::entityQuery('node')
718
        ->condition('uid', $account->id())
719 720
        ->execute();
      module_load_include('inc', 'node', 'node.admin');
721
      node_mass_update($nids, ['status' => 0], NULL, TRUE);
722 723 724
      break;

    case 'user_cancel_reassign':
725
      // Anonymize all of the nodes for this old account.
726
      module_load_include('inc', 'node', 'node.admin');
727
      $vids = \Drupal::entityManager()->getStorage('node')->userRevisionIds($account);
728
      node_mass_update($vids, [
729 730
        'uid' => 0,
        'revision_uid' => 0,
731
      ], NULL, TRUE, TRUE);
732
      break;
733 734
  }
}
735

736
/**
737
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
738
 */
739
function node_user_predelete($account) {
740 741
  // Delete nodes (current revisions).
  // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
742
  $nids = \Drupal::entityQuery('node')
743
    ->condition('uid', $account->id())
744
    ->accessCheck(FALSE)
745 746
    ->execute();
  entity_delete_multiple('node', $nids);
747
  // Delete old revisions.