node.module 50.7 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
use Drupal\user\UserInterface;
32

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

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

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

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

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

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

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

111 112
  switch ($route_name) {
    case 'help.page.node':
113 114
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
115
      $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>';
116 117
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
118
      $output .= '<dt>' . t('Creating content') . '</dt>';
119
      $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>';
120
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
121
      $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>';
122
      $output .= '<dt>' . t('Administering content') . '</dt>';
123
      $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>';
124 125
      $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>';
126
      $output .= '<dt>' . t('User permissions') . '</dt>';
127
      $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>';
128
      $output .= '</dl>';
129
      return $output;
130

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

394
  return $field;
395
}
396

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

  return $extra;
413 414
}

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

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

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

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

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

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

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

547 548 549 550
/**
 * Implements hook_preprocess_HOOK() for HTML document templates.
 */
function node_preprocess_html(&$variables) {
551 552 553 554 555 556
  // 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();
    }
557 558 559
  }
}

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

573 574 575 576
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
577
  $suggestions = [];
578
  $node = $variables['elements']['#node'];
579
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
580

581
  $suggestions[] = 'node__' . $sanitized_view_mode;
582
  $suggestions[] = 'node__' . $node->bundle();
583
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
584
  $suggestions[] = 'node__' . $node->id();
585
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
586 587 588 589

  return $suggestions;
}

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

617
  $variables['url'] = $node->url('canonical', [
618
    'language' => $node->language(),
619
  ]);
620 621
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
622 623 624
  // 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'.
625
  $variables['page'] = ($variables['view_mode'] == 'full' && (node_is_page($node)) || (isset($node->in_preview) && in_array($node->preview_view_mode, ['full', 'default'])));
626

627
  // Helpful $content variable for templates.
628
  $variables += ['content' => []];
629
  foreach (Element::children($variables['elements']) as $key) {
630 631
    $variables['content'][$key] = $variables['elements'][$key];
  }
632 633

  // Display post information only on certain node types.
634
  $node_type = $node->type->entity;
635 636
  // Used by RDF to add attributes around the author and date submitted.
  $variables['author_attributes'] = new Attribute();
637
  $variables['display_submitted'] = $node_type->displaySubmitted();
638
  if ($variables['display_submitted']) {
639
    if (theme_get_setting('features.node_user_picture')) {
640
      // To change user picture settings (e.g. image style), edit the 'compact'
641 642
      // 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.
643
      $variables['author_picture'] = user_view($node->getOwner(), 'compact');
644
    }
645
  }
646

647
  // Add article ARIA role.
648
  $variables['attributes']['role'] = 'article';
649 650
}

651 652 653 654 655 656 657
/**
 * 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.)
658 659 660 661 662 663 664 665 666
  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.
667
      $array = [
668 669
        'min_created' => $result[0][$min_alias],
        'max_created' => $result[0][$max_alias],
670
      ];
671 672
      \Drupal::state()->set('node.min_max_update_time', $array);
    }
673 674 675
  }
}

676
/**
677
 * Implements hook_ranking().
678 679 680
 */
function node_ranking() {
  // Create the ranking array and add the basic ranking options.
681 682
  $ranking = [
    'relevance' => [
683 684 685
      'title' => t('Keyword relevance'),
      // Average relevance values hover around 0.15
      'score' => 'i.relevance',
686 687
    ],
    'sticky' => [
688 689 690
      'title' => t('Content is sticky at top of lists'),
      // The sticky flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.sticky',
691 692
    ],
    'promote' => [
693 694 695
      'title' => t('Content is promoted to the front page'),
      // The promote flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.promote',
696 697
    ],
  ];
698 699 700
  // 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')) {
701
    $ranking['recent'] = [
702 703
      'title' => t('Recently created'),
      // Exponential decay with half life of 14% of the age range of nodes.
704
      'score' => 'EXP(-5 * (1 - (n.created - :node_oldest) / :node_range))',
705
      'arguments' => [
706 707
        ':node_oldest' => $node_min_max['min_created'],
        ':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
708 709
      ],
    ];
710 711 712 713
  }
  return $ranking;
}

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

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

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