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

Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert committed
9
10
 */

11
use Drupal\Component\Utility\Html;
12
use Drupal\Component\Utility\Xss;
13
use Drupal\Core\Access\AccessResult;
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
const NODE_NOT_PUBLISHED = 0;
36
37

/**
38
 * Denotes that the node is published.
39
 */
40
const NODE_PUBLISHED = 1;
41
42

/**
43
 * Denotes that the node is not promoted to the front page.
44
 */
45
const NODE_NOT_PROMOTED = 0;
46
47

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

/**
53
 * Denotes that the node is not sticky at the top of the page.
54
 */
55
const NODE_NOT_STICKY = 0;
56
57

/**
58
 * Denotes that the node is sticky at the top of the page.
59
 */
60
const NODE_STICKY = 1;
61

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

80
81
  switch ($route_name) {
    case 'help.page.node':
82
83
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
84
      $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>';
85
86
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
87
      $output .= '<dt>' . t('Creating content') . '</dt>';
88
      $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>';
89
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
90
      $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>';
91
      $output .= '<dt>' . t('Administering content') . '</dt>';
92
      $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>';
93
94
      $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>';
95
      $output .= '<dt>' . t('User permissions') . '</dt>';
96
      $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>';
97
      $output .= '</dl>';
98
      return $output;
99

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

103
104
    case 'field_ui.form_display_overview_node':
    case 'field_ui.form_display_overview_form_mode_node':
105
      $type = $route_match->getParameter('node_type');
106
      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>' ;
107

108
109
    case 'field_ui.display_overview_node':
    case 'field_ui.display_overview_view_mode_node':
110
      $type = $route_match->getParameter('node_type');
111
      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>' .
112
        '<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>';
113

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

117
    case 'entity.node.edit_form':
118
      $node = $route_match->getParameter('node');
119
      $type = NodeType::load($node->getType());
120
121
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
Dries Buytaert's avatar
   
Dries Buytaert committed
122

123
    case 'node.add':
124
      $type = $route_match->getParameter('node_type');
125
126
      $help = $type->getHelp();
      return (!empty($help) ? Xss::filterAdmin($help) : '');
127
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
128
129
}

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

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

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

200
  return $num_rows ? array('#theme' => 'item_list__node', '#items' => $items, '#title' => $title) : FALSE;
Dries Buytaert's avatar
   
Dries Buytaert committed
201
202
}

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

216
  $cache = &drupal_static(__FUNCTION__, array());
Dries Buytaert's avatar
   
Dries Buytaert committed
217

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

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

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

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

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

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

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

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

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

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

361
  return $field;
362
}
363

364
/**
365
 * Implements hook_entity_extra_field_info().
366
 */
367
function node_entity_extra_field_info() {
368
  $extra = array();
369
370
  $description = t('Node module element');
  foreach (node_type_get_types() as $bundle) {
371
    $extra['node'][$bundle->id()]['display']['links'] = array(
372
373
374
375
376
      'label' => t('Links'),
      'description' => $description,
      'weight' => 100,
      'visible' => TRUE,
    );
377
  }
378
379

  return $extra;
380
381
}

382
/**
383
384
 * Updates all nodes of one type to be of another type.
 *
385
 * @param string $old_id
386
 *   The current node type of the nodes.
387
 * @param string $new_id
388
 *   The new node type of the nodes.
389
390
 *
 * @return
391
 *   The number of nodes whose node type field was modified.
392
 */
393
function node_type_update_nodes($old_id, $new_id) {
394
  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
Dries Buytaert's avatar
   
Dries Buytaert committed
395
}
Dries Buytaert's avatar
   
Dries Buytaert committed
396

397
/**
398
 * Loads node entities from the database.
399
400
 *
 * This function should be used whenever you need to load more than one node
401
402
 * from the database. Nodes are loaded into memory and will not require database
 * access if loaded again during the same page request.
Dries Buytaert's avatar
   
Dries Buytaert committed
403
 *
404
405
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
406
 * @param bool $reset
407
408
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries Buytaert's avatar
   
Dries Buytaert committed
409
 *
410
 * @return \Drupal\node\NodeInterface[]
411
 *   An array of node entities indexed by nid.
412
 *
413
414
415
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::loadMultiple().
 *
416
 * @see entity_load_multiple()
417
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
Dries Buytaert's avatar
   
Dries Buytaert committed
418
 */
419
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
420
421
422
423
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache($nids);
  }
  return Node::loadMultiple($nids);
424
425
426
}

/**
427
 * Loads a node entity from the database.
428
 *
429
 * @param int $nid
430
 *   The node ID.
431
 * @param bool $reset
432
433
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
434
 *
435
436
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
437
438
439
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\node\Entity\Node::load().
440
 */
441
function node_load($nid = NULL, $reset = FALSE) {
442
443
444
445
  if ($reset) {
    \Drupal::entityManager()->getStorage('node')->resetCache(array($nid));
  }
  return Node::load($nid);
446
447
448
449
450
}

/**
 * Loads a node revision from the database.
 *
451
 * @param int $vid
452
453
 *   The node revision id.
 *
454
455
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
456
457
458
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries Buytaert's avatar
   
Dries Buytaert committed
459
460
}

461
/**
462
 * Deletes a node revision.
463
464
465
 *
 * @param $revision_id
 *   The revision ID to delete.
466
467
 *
 * @return
468
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
469
470
 */
function node_revision_delete($revision_id) {
471
  entity_revision_delete('node', $revision_id);
472
473
}

474
/**
475
 * Checks whether the current page is the full page view of the passed-in node.
476
 *
477
 * @param \Drupal\node\NodeInterface $node
478
 *   A node entity.
479
480
481
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
482
 */
483
function node_is_page(NodeInterface $node) {
484
  $route_match = \Drupal::routeMatch();
485
  if ($route_match->getRouteName() == 'entity.node.canonical') {
486
    $page_node = $route_match->getParameter('node');
487
  }
488
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
489
490
}

491
492
493
494
495
/**
 * 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.
496
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
497
    $variables['node_type'] = $node->getType();
498
499
500
  }
}

501
/**
502
 * Implements hook_preprocess_HOOK() for block templates.
503
504
 */
function node_preprocess_block(&$variables) {
505
  if ($variables['configuration']['provider'] == 'node') {
506
    switch ($variables['elements']['#plugin_id']) {
507
      case 'node_syndicate_block':
508
        $variables['attributes']['role'] = 'complementary';
509
510
511
512
513
        break;
    }
  }
}

514
515
516
517
518
519
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
  $suggestions = array();
  $node = $variables['elements']['#node'];
520
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
521

522
  $suggestions[] = 'node__' . $sanitized_view_mode;
523
  $suggestions[] = 'node__' . $node->bundle();
524
  $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
525
  $suggestions[] = 'node__' . $node->id();
526
  $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
527
528
529
530

  return $suggestions;
}

531
/**
532
 * Prepares variables for node templates.
533
 *
534
 * Default template: node.html.twig.
535
 *
536
537
538
 * 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.
539
540
 *
 * @param array $variables
541
542
543
544
 *   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'...
545
546
 */
function template_preprocess_node(&$variables) {
547
  $variables['view_mode'] = $variables['elements']['#view_mode'];
548
  // Provide a distinct $teaser boolean.
549
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
550
  $variables['node'] = $variables['elements']['#node'];
551
  /** @var \Drupal\node\NodeInterface $node */
552
  $node = $variables['node'];
553
  $variables['date'] = drupal_render($variables['elements']['created']);
554
  unset($variables['elements']['created']);
555
  $variables['author_name'] = drupal_render($variables['elements']['uid']);
556
  unset($variables['elements']['uid']);
557

558
  $variables['url'] = $node->url('canonical', array(
559
560
    'language' => $node->language(),
  ));
561
562
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
563
564
565
566
  // 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'))));
567

568
  // Helpful $content variable for templates.
569
  $variables += array('content' => array());
570
  foreach (Element::children($variables['elements']) as $key) {
571
572
    $variables['content'][$key] = $variables['elements'][$key];
  }
573
574

  // Display post information only on certain node types.
575
  $node_type = $node->type->entity;
576
577
  // Used by RDF to add attributes around the author and date submitted.
  $variables['author_attributes'] = new Attribute();
578
  $variables['display_submitted'] = $node_type->displaySubmitted();
579
  if ($variables['display_submitted']) {
580
    if (theme_get_setting('features.node_user_picture')) {
581
      // To change user picture settings (e.g. image style), edit the 'compact'
582
583
      // 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.
584
      $variables['author_picture'] = user_view($node->getOwner(), 'compact');
585
    }
586
  }
587

588
  // Add article ARIA role.
589
  $variables['attributes']['role'] = 'article';
590
591
}

592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
/**
 * 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);
  }
}

615
/**
616
 * Implements hook_ranking().
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
 */
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',
    ),
  );
637
638
639
  // 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')) {
640
    $ranking['recent'] = array(
641
642
643
644
645
646
647
648
649
650
651
652
653
      '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),
      ),
654
655
656
657
658
    );
  }
  return $ranking;
}

659
/**
660
 * Implements hook_user_cancel().
661
 */
662
function node_user_cancel($edit, $account, $method) {
663
664
665
  switch ($method) {
    case 'user_cancel_block_unpublish':
      // Unpublish nodes (current revisions).
666
      $nids = \Drupal::entityQuery('node')
667
        ->condition('uid', $account->id())
668
669
670
        ->execute();
      module_load_include('inc', 'node', 'node.admin');
      node_mass_update($nids, array('status' => 0), NULL, TRUE);
671
672
673
      break;

    case 'user_cancel_reassign':
674
      // Anonymize all of the nodes for this old account.
675
      module_load_include('inc', 'node', 'node.admin');
676
      $vids = \Drupal::entityManager()->getStorage('node')->userRevisionIds($account);
677
678
679
680
      node_mass_update($vids, array(
        'uid' => 0,
        'revision_uid' => 0,
      ), NULL, TRUE, TRUE);
681
      break;
682
683
  }
}
684

685
/**
686
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
687
 */
688
function node_user_predelete($account) {
689
690
  // Delete nodes (current revisions).
  // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
691
  $nids = \Drupal::entityQuery('node')
692
    ->condition('uid', $account->id())
693
694
    ->execute();
  entity_delete_multiple('node', $nids);
695
  // Delete old revisions.
696
697
  $storage_controller = \Drupal::entityManager()->getStorage('node');
  $revisions = $storage_controller->userRevisionIds($account);
698
699
  foreach ($revisions as $revision) {
    node_revision_delete($revision);
700
  }
701
702
}

703
/**
704
705
 * Title callback: Displays the node's title.
 *
706
 * @param \Drupal\node\NodeInterface $node
707
 *   The node entity.
708
 *
709
710
711
 * @return
 *   An unsanitized string that is the title of the node.
 *
712
 * @see node_menu()
713
 */
714
function node_page_title(NodeInterface $node) {
715
  return $node->label();
716
717
}

718
719
720
721
722
/**
 * Finds the last time a node was changed.
 *
 * @param $nid
 *   The ID of a node.
723
724
725
 * @param string $langcode
 *   (optional) The language the node has been last modified in. Defaults to the
 *   node language.
726
 *
727
 * @return string
728
 *   A unix timestamp indicating the last time the node was changed.
729
 *
730
731
 * @todo Remove once https://drupal.org/node/2002180 is resolved. It's only used
 *       for validation, which will be done by EntityChangedConstraintValidator.
732
 */
733
734
735
function node_last_changed($nid, $langcode = NULL) {
  $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime();
  return $changed ? $changed : FALSE;
736
737
}

738
/**
739
 * Finds the most recently changed nodes that are available to the current user.
740
741
742
743
744
 *
 * @param $number
 *   (optional) The maximum number of nodes to find. Defaults to 10.
 *
 * @return
745
746
 *   An array of node entities or an empty array if there are no recent nodes
 *   visible to the current user.
747
748
 */
function node_get_recent($number = 10) {
749
750
  $account = \Drupal::currentUser();
  $query = \Drupal::entityQuery('node');
751

752
  if (!$account->hasPermission('bypass node access')) {
753
754
755
    // If the user is able to view their own unpublished nodes, allow them
    // to see these in addition to published nodes. Check that they actually
    // have some unpublished nodes to view before adding the condition.
756
757
758
759
760
761
762
    $access_query =  \Drupal::entityQuery('node')
      ->condition('uid', $account->id())
      ->condition('status', NODE_NOT_PUBLISHED);
    if ($account->hasPermission('view own unpublished content') && ($own_unpublished = $access_query->execute())) {
      $query->orConditionGroup()
        ->condition('status', NODE_PUBLISHED)
        ->condition('nid', $own_unpublished, 'IN');
763
764
765
    }
    else {
      // If not, restrict the query to published nodes.
766
      $query->condition('status', NODE_PUBLISHED);
767
    }
768
   }
769
  $nids = $query
770
    ->sort('changed', 'DESC')
771
772
    ->range(0, $number)
    ->addTag('node_access')
773
    ->execute();