node.module 49.9 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\Xss;
12
use Drupal\Core\Access\AccessResult;
13
use Drupal\Core\Form\FormStateInterface;
14
use Drupal\Core\Language\LanguageInterface;
15
use Drupal\Core\Render\Element;
16
use Drupal\Core\Routing\RouteMatchInterface;
17
use Drupal\Core\Url;
18
use Symfony\Component\HttpFoundation\Response;
19
use Drupal\Core\Database\StatementInterface;
20 21
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
22
use Drupal\field\Entity\FieldStorageConfig;
23
use Drupal\field\Entity\FieldConfig;
24 25
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
26
use Drupal\node\NodeTypeInterface;
27
use Drupal\node\NodeInterface;
28
use Drupal\Core\Entity\EntityInterface;
29 30
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
31
use Drupal\Core\Template\Attribute;
32
use Drupal\file\Entity\File;
33
use Drupal\language\ConfigurableLanguageInterface;
34 35
use Drupal\block\Entity\Block;
use Drupal\Core\Session\AccountInterface;
36

37
/**
38
 * Denotes that the node is not published.
39
 */
40
const NODE_NOT_PUBLISHED = 0;
41 42

/**
43
 * Denotes that the node is published.
44
 */
45
const NODE_PUBLISHED = 1;
46 47

/**
48
 * Denotes that the node is not promoted to the front page.
49
 */
50
const NODE_NOT_PROMOTED = 0;
51 52

/**
53
 * Denotes that the node is promoted to the front page.
54
 */
55
const NODE_PROMOTED = 1;
56 57

/**
58
 * Denotes that the node is not sticky at the top of the page.
59
 */
60
const NODE_NOT_STICKY = 0;
61 62

/**
63
 * Denotes that the node is sticky at the top of the page.
64
 */
65
const NODE_STICKY = 1;
66

Dries's avatar
 
Dries committed
67
/**
68
 * Implements hook_help().
Dries's avatar
 
Dries committed
69
 */
70
function node_help($route_name, RouteMatchInterface $route_match) {
71 72 73
  // 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.
74
  if ($route_name != 'node.configure_rebuild_confirm' && $route_name != 'system.batch_page.normal' && $route_name != 'help.page.node' && $route_name != 'help.main'
75
    && \Drupal::currentUser()->hasPermission('access administration pages') && node_access_needs_rebuild()) {
76
    if ($route_name == 'system.status') {
77 78 79
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
80
      $message = t('The content access permissions need to be rebuilt. <a href="!node_access_rebuild">Rebuild permissions</a>.', array('!node_access_rebuild' => \Drupal::url('node.configure_rebuild_confirm')));
81 82 83 84
    }
    drupal_set_message($message, 'error');
  }

85 86
  switch ($route_name) {
    case 'help.page.node':
87 88
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
89
      $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 <a href="!node">the online documentation for the Node module</a>.', array('!node' => 'https://drupal.org/documentation/modules/node', '!field' => \Drupal::url('help.page', array('name' => 'field')))) . '</p>';
90 91
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
92
      $output .= '<dt>' . t('Creating content') . '</dt>';
93
      $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.', array('!content-type' => \Drupal::url('node.overview_types'))) . '</dd>';
94
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
95
      $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 allows you the flexibility to add <a href="!field">fields</a> and configure default settings that suit the differing needs of various site content.', array('!content-new' => \Drupal::url('node.type_add'), '!field' => \Drupal::url('help.page', array('name' => 'field')))) . '</dd>';
96
      $output .= '<dt>' . t('Administering content') . '</dt>';
97
      $output .= '<dd>' . t('The <a href="!content">Content administration page</a> allows you to review and bulk manage your site content.', array('!content' => \Drupal::url('system.admin_content'))) . '</dd>';
98 99
      $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>';
100
      $output .= '<dt>' . t('User permissions') . '</dt>';
101
      $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>.', array('!permissions' => \Drupal::url('user.admin_permissions', array(), array('fragment' => 'module-node')))) . '</dd>';
102
      $output .= '</dl>';
103
      return $output;
104

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

108 109
    case 'field_ui.form_display_overview_node':
    case 'field_ui.form_display_overview_form_mode_node':
110
      $type = $route_match->getParameter('node_type');
111
      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.', array('%type' => $type->label())) . '</p>' ;
112

113 114
    case 'field_ui.display_overview_node':
    case 'field_ui.display_overview_view_mode_node':
115
      $type = $route_match->getParameter('node_type');
116
      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>' .
117
        '<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.', array('%type' => $type->label())) . '</p>';
118

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

122
    case 'entity.node.edit_form':
123
      $node = $route_match->getParameter('node');
124
      $type = NodeType::load($node->getType());
125
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
Dries's avatar
 
Dries committed
126

127
    case 'node.add':
128
      $type = $route_match->getParameter('node_type');
129
      return (!empty($type->help) ? Xss::filterAdmin($type->help) : '');
130
  }
Dries's avatar
 
Dries committed
131 132
}

133
/**
134
 * Implements hook_theme().
135 136 137
 */
function node_theme() {
  return array(
138
    'node' => array(
139
      'render element' => 'elements',
140
    ),
141
    'node_add_list' => array(
142
      'variables' => array('content' => NULL),
143
      'file' => 'node.pages.inc',
144
    ),
145 146 147
    'node_edit_form' => array(
      'render element' => 'form',
    ),
148 149 150
    'field__node__title' => array(
      'base hook' => 'field',
    ),
151 152 153 154 155 156
    'field__node__uid' => array(
      'base hook' => 'field',
    ),
    'field__node__created' => array(
      'base hook' => 'field',
    ),
157 158 159
  );
}

160
/**
161
 * Implements hook_entity_view_display_alter().
162
 */
163
function node_entity_view_display_alter(EntityViewDisplayInterface $display, $context) {
164 165 166 167 168 169 170 171
  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);
        }
172 173
      }
    }
174 175 176
  }
}

Dries's avatar
 
Dries committed
177
/**
178
 * Gathers a listing of links to nodes.
Dries's avatar
 
Dries committed
179
 *
180
 * @param \Drupal\Core\Database\StatementInterface $result
181
 *   A database result object from a query to fetch node entities. If your
182
 *   query joins the {comment_entity_statistics} table so that the comment_count
183 184
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
 
Dries committed
185
 * @param $title
186
 *   (optional) A heading for the resulting list.
Dries's avatar
 
Dries committed
187 188
 *
 * @return
189 190
 *   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
191
 */
192
function node_title_list(StatementInterface $result, $title = NULL) {
193
  $items = array();
194
  $num_rows = FALSE;
195 196 197 198
  foreach ($result as $row) {
    // Do not use $node->label() or $node->urlInfo() here, because we only have
    // database rows, not actual nodes.
    $options = !empty($row->comment_count) ? array('attributes' => array('title' => format_plural($row->comment_count, '1 comment', '@count comments'))) : array();
199
    $items[] = \Drupal::l($row->title, new Url('entity.node.canonical', ['node' => $row->nid], $options));
200
    $num_rows = TRUE;
Dries's avatar
 
Dries committed
201 202
  }

203
  return $num_rows ? array('#theme' => 'item_list__node', '#items' => $items, '#title' => $title) : FALSE;
Dries's avatar
 
Dries committed
204 205
}

Dries's avatar
 
Dries committed
206
/**
207
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
208
 *
209
 * @param int $nid
Dries's avatar
 
Dries committed
210
 *   Node ID whose history supplies the "last viewed" timestamp.
211
 * @param int $timestamp
Dries's avatar
 
Dries committed
212
 *   Time which is compared against node's "last viewed" timestamp.
213
 *
214
 * @return int
215
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
216
 */
217
function node_mark($nid, $timestamp) {
218

219
  $cache = &drupal_static(__FUNCTION__, array());
Dries's avatar
 
Dries committed
220

221
  if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
222 223
    return MARK_READ;
  }
Dries's avatar
Dries committed
224
  if (!isset($cache[$nid])) {
225
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
226
  }
227
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
228 229
    return MARK_NEW;
  }
230
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
231 232 233
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
234 235
}

236 237 238
/**
 * Returns a list of all the available node types.
 *
239 240
 * This list can include types that are queued for addition or deletion.
 *
241
 * @return \Drupal\node\NodeTypeInterface[]
242
 *   An array of node type entities, keyed by ID.
243
 *
244 245 246
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::loadMultiple().
 *
247
 * @see node_type_load()
248 249
 */
function node_type_get_types() {
250
  return NodeType::loadMultiple();
251 252 253
}

/**
254 255 256
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
257
 *
258
 * @return string[]
259
 *   An array of node type labels, keyed by the node type name.
260 261
 */
function node_type_get_names() {
262 263 264
  return array_map(function ($bundle_info) {
    return $bundle_info['label'];
  }, \Drupal::entityManager()->getBundleInfo('node'));
265 266 267 268 269
}

/**
 * Returns the node type label for the passed node.
 *
270
 * @param \Drupal\node\NodeInterface $node
271 272 273 274
 *   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.
275
 *
276 277
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
278
 */
279
function node_get_type_label(NodeInterface $node) {
280 281
  $type = entity_load('node_type', $node->bundle());
  return $type ? $type->label() : FALSE;
282 283 284 285 286
}

/**
 * Description callback: Returns the node type description.
 *
287
 * @param \Drupal\node\NodeTypeInterface $node_type
288 289
 *   The node type object.
 *
290
 * @return string
291 292
 *   The node type description.
 */
293
function node_type_get_description(NodeTypeInterface $node_type) {
294 295 296
  return $node_type->description;
}

297
/**
298
 * Menu argument loader: Loads a node type by string.
299 300
 *
 * @param $name
301
 *   The machine name of a node type to load.
302
 *
303
 * @return \Drupal\node\NodeTypeInterface
304
 *   A node type object or NULL if $name does not exist.
305 306 307
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\NodeType::load().
308 309
 */
function node_type_load($name) {
310
  return NodeType::load($name);
311
}
312

313
/**
314
 * Adds the default body field to a node type.
315
 *
316
 * @param \Drupal\node\NodeTypeInterface $type
317
 *   A node type object.
318
 * @param string $label
319
 *   (optional) The label for the body instance.
320
 *
321
 * @return \Drupal\field\Entity\FieldConfig Body field.
322
 */
323
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
324
   // Add or remove the body field, as needed.
325
  $field_storage = FieldStorageConfig::loadByName('node', 'body');
326 327 328
  $field = FieldConfig::loadByName('node', $type->id(), 'body');
  if (empty($field)) {
    $field = entity_create('field_config', array(
329
      'field_storage' => $field_storage,
330
      'bundle' => $type->id(),
331 332
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
333
    ));
334
    $field->save();
335

336 337
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
338
      ->setComponent('body', array(
339 340 341 342
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

343 344
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
345
      ->setComponent('body', array(
346 347 348 349
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
350 351 352

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
353
    $view_modes = \Drupal::entityManager()->getViewModes('node');
354 355 356 357 358 359 360 361
    if (isset($view_modes['teaser'])) {
      entity_get_display('node', $type->type, 'teaser')
        ->setComponent('body', array(
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ))
        ->save();
    }
362
  }
363

364
  return $field;
365
}
366

367
/**
368
 * Implements hook_entity_extra_field_info().
369
 */
370
function node_entity_extra_field_info() {
371
  $extra = array();
372
  $module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
373 374 375
  $description = t('Node module element');

  foreach (node_type_get_types() as $bundle) {
376 377
    // Add the 'language' select if Language module is enabled and the bundle
    // has multilingual support.
378
    // Visibility of the ordering of the language selector is the same as on the
379 380 381
    // node/add form.
    if ($module_language_enabled) {
      $configuration = language_get_default_configuration('node', $bundle->type);
382
      if ($configuration['language_show']) {
383
        $extra['node'][$bundle->type]['form']['langcode'] = array(
384 385 386 387 388
          'label' => t('Language'),
          'description' => $description,
          'weight' => 0,
        );
      }
389
    }
390
    $extra['node'][$bundle->type]['display']['langcode'] = array(
391 392 393 394 395
      'label' => t('Language'),
      'description' => $description,
      'weight' => 0,
      'visible' => FALSE,
    );
396 397 398 399 400 401
    $extra['node'][$bundle->type]['display']['links'] = array(
      'label' => t('Links'),
      'description' => $description,
      'weight' => 100,
      'visible' => TRUE,
    );
402
  }
403 404

  return $extra;
405 406
}

407
/**
408 409
 * Updates all nodes of one type to be of another type.
 *
410
 * @param string $old_id
411
 *   The current node type of the nodes.
412
 * @param string $new_id
413
 *   The new node type of the nodes.
414 415
 *
 * @return
416
 *   The number of nodes whose node type field was modified.
417
 */
418
function node_type_update_nodes($old_id, $new_id) {
419
  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
Dries's avatar
 
Dries committed
420
}
Dries's avatar
 
Dries committed
421

422
/**
423
 * Loads node entities from the database.
424 425
 *
 * This function should be used whenever you need to load more than one node
426 427
 * 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
428
 *
429 430
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
431
 * @param bool $reset
432 433
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries's avatar
 
Dries committed
434
 *
435
 * @return \Drupal\node\NodeInterface[]
436
 *   An array of node entities indexed by nid.
437
 *
438 439 440
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::loadMultiple().
 *
441
 * @see entity_load_multiple()
442
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
Dries's avatar
 
Dries committed
443
 */
444
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
445 446 447 448
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache($nids);
  }
  return Node::loadMultiple($nids);
449 450 451
}

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

/**
 * Loads a node revision from the database.
 *
476
 * @param int $vid
477 478
 *   The node revision id.
 *
479 480
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
481 482 483
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries's avatar
 
Dries committed
484 485
}

486
/**
487
 * Deletes a node revision.
488 489 490
 *
 * @param $revision_id
 *   The revision ID to delete.
491 492
 *
 * @return
493
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
494 495
 */
function node_revision_delete($revision_id) {
496
  entity_revision_delete('node', $revision_id);
497 498
}

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

516 517 518 519 520
/**
 * 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.
521
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
522
    $variables['attributes']['class'][] = drupal_html_class('node--type-' . $node->getType());
523 524 525
  }
}

526
/**
527
 * Implements hook_preprocess_HOOK() for block templates.
528 529
 */
function node_preprocess_block(&$variables) {
530
  if ($variables['configuration']['provider'] == 'node') {
531
    switch ($variables['elements']['#plugin_id']) {
532
      case 'node_syndicate_block':
533
        $variables['attributes']['role'] = 'complementary';
534 535 536 537 538
        break;
    }
  }
}

539 540 541 542 543 544
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
  $suggestions = array();
  $node = $variables['elements']['#node'];
545
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
546

547
  $suggestions[] = 'node__' . $sanitized_view_mode;
548
  $suggestions[] = 'node__' . $node->bundle();
549
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
550
  $suggestions[] = 'node__' . $node->id();
551
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
552 553 554 555

  return $suggestions;
}

556
/**
557
 * Prepares variables for node templates.
558
 *
559
 * Default template: node.html.twig.
560
 *
561 562 563
 * 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.
564 565
 *
 * @param array $variables
566 567 568 569
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - node: The node object.
 *   - view_mode: View mode; e.g., 'full', 'teaser'...
570 571
 */
function template_preprocess_node(&$variables) {
572
  $variables['view_mode'] = $variables['elements']['#view_mode'];
573
  // Provide a distinct $teaser boolean.
574
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
575
  $variables['node'] = $variables['elements']['#node'];
576
  /** @var \Drupal\node\NodeInterface $node */
577
  $node = $variables['node'];
578
  $variables['date'] = drupal_render($variables['elements']['created']);
579
  unset($variables['elements']['created']);
580
  $variables['author_name'] = drupal_render($variables['elements']['uid']);
581
  unset($variables['elements']['uid']);
582

583
  $variables['url'] = $node->url('canonical', array(
584 585
    'language' => $node->language(),
  ));
586 587
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
588 589 590 591
  // 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'.
  $variables['page'] = ($variables['view_mode'] == 'full' && (node_is_page($node)) || (isset($node->in_preview) && in_array($node->preview_view_mode, array('full', 'default'))));
592

593
  // Helpful $content variable for templates.
594
  $variables += array('content' => array());
595
  foreach (Element::children($variables['elements']) as $key) {
596 597
    $variables['content'][$key] = $variables['elements'][$key];
  }
598 599

  // Display post information only on certain node types.
600
  $node_type = $node->type->entity;
601 602
  // Used by RDF to add attributes around the author and date submitted.
  $variables['author_attributes'] = new Attribute();
603
  $variables['display_submitted'] = $node_type->displaySubmitted();
604
  if ($variables['display_submitted']) {
605
    if (theme_get_setting('features.node_user_picture')) {
606
      // To change user picture settings (e.g. image style), edit the 'compact'
607 608
      // 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.
609
      $variables['author_picture'] = user_view($node->getOwner(), 'compact');
610
    }
611
  }
612

613
  // Add article ARIA role.
614
  $variables['attributes']['role'] = 'article';
615 616
}

617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
/**
 * 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.)
  $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.
    $array = array(
      'min_created' => $result[0][$min_alias],
      'max_created' => $result[0][$max_alias],
    );
    \Drupal::state()->set('node.min_max_update_time', $array);
  }
}

640
/**
641
 * Implements hook_ranking().
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
 */
function node_ranking() {
  // Create the ranking array and add the basic ranking options.
  $ranking = array(
    'relevance' => array(
      'title' => t('Keyword relevance'),
      // Average relevance values hover around 0.15
      'score' => 'i.relevance',
    ),
    'sticky' => array(
      'title' => t('Content is sticky at top of lists'),
      // The sticky flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.sticky',
    ),
    'promote' => array(
      'title' => t('Content is promoted to the front page'),
      // The promote flag is either 0 or 1, which is automatically normalized.
      'score' => 'n.promote',
    ),
  );
662 663 664
  // 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')) {
665
    $ranking['recent'] = array(
666 667 668 669 670 671 672 673 674 675 676 677 678
      'title' => t('Recently created'),
      'join' => array(
        'type' => 'LEFT',
        'table' => 'node_field_data',
        'alias' => 'nfd',
        'on' => 'nfd.nid = sid',
      ),
      // Exponential decay with half life of 14% of the age range of nodes.
      'score' => 'EXP(-5 * (1 - (nfd.created - :node_oldest) / :node_range))',
      'arguments' => array(
        ':node_oldest' => $node_min_max['min_created'],
        ':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
      ),
679 680 681 682 683
    );
  }
  return $ranking;
}

684
/**
685
 * Implements hook_user_cancel().
686
 */
687
function node_user_cancel($edit, $account, $method) {
688 689 690
  switch ($method) {
    case 'user_cancel_block_unpublish':
      // Unpublish nodes (current revisions).
691
      $nids = \Drupal::entityQuery('node')
692
        ->condition('uid', $account->id())
693 694 695
        ->execute();
      module_load_include('inc', 'node', 'node.admin');
      node_mass_update($nids, array('status' => 0), NULL, TRUE);
696 697 698
      break;

    case 'user_cancel_reassign':
699
      // Anonymize all of the nodes for this old account.
700
      module_load_include('inc', 'node', 'node.admin');
701
      $vids = \Drupal::entityManager()->getStorage('node')->userRevisionIds($account);
702 703 704 705
      node_mass_update($vids, array(
        'uid' => 0,
        'revision_uid' => 0,
      ), NULL, TRUE, TRUE);
706
      break;
707 708
  }
}
709

710
/**
711
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
712
 */
713
function node_user_predelete($account) {
714 715
  // Delete nodes (current revisions).
  // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
716
  $nids = \Drupal::entityQuery('node')
717
    ->condition('uid', $account->id())
718 719
    ->execute();
  entity_delete_multiple('node', $nids);
720
  // Delete old revisions.
721 722
  $storage_controller = \Drupal::entityManager()->getStorage('node');
  $revisions = $storage_controller->userRevisionIds($account);
723 724
  foreach ($revisions as $revision) {
    node_revision_delete($revision);
725
  }
726 727
}

728
/**
729 730
 * Title callback: Displays the node's title.
 *
731
 * @param \Drupal\node\NodeInterface $node
732
 *   The node entity.
733
 *
734 735 736
 * @return
 *   An unsanitized string that is the title of the node.
 *
737
 * @see node_menu()
738
 */
739
function node_page_title(NodeInterface $node) {
740
  return $node->label();
741 742
}

743 744 745 746 747
/**
 * Finds the last time a node was changed.
 *
 * @param $nid
 *   The ID of a node.
748 749 750
 * @param string $langcode
 *   (optional) The language the node has been last modified in. Defaults to the
 *   node language.
751
 *
752
 * @return string
753
 *   A unix timestamp indicating the last time the node was changed.
754
 *
755 756
 * @todo Remove once https://drupal.org/node/2002180 is resolved. It's only used
 *       for validation, which will be done by EntityChangedConstraintValidator.
757
 */
758 759 760
function node_last_changed($nid, $langcode = NULL) {
  $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime();
  return $changed ? $changed : FALSE;