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

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

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

32
/**
33
 * Denotes that the node is not published.
34
 */
35
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's avatar
   
Dries committed
62
/**
63
 * Implements hook_help().
Dries's avatar
   
Dries 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 the <a href=":node">online documentation for the Node module</a>.', array(':node' => 'https://www.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('entity.node_type.collection'))) . '</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 gives 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</a> page lists your content, allowing you add new content, filter, edit or delete existing content, or perform bulk operations on existing 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 'entity.entity_form_display.node.default':
    case 'entity.entity_form_display.node.form_mode':
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 'entity.entity_view_display.node.default':
    case 'entity.entity_view_display.node.view_mode':
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 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's avatar
   
Dries 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's avatar
   
Dries 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
    ),
141
142
143
    'node_edit_form' => array(
      'render element' => 'form',
    ),
144
145
146
    'field__node__title' => array(
      'base hook' => 'field',
    ),
147
148
149
150
151
152
    'field__node__uid' => array(
      'base hook' => 'field',
    ),
    'field__node__created' => array(
      'base hook' => 'field',
    ),
153
154
155
  );
}

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

Dries's avatar
   
Dries committed
173
/**
174
 * Gathers a listing of links to nodes.
Dries's avatar
   
Dries committed
175
 *
176
 * @param \Drupal\Core\Database\StatementInterface $result
177
 *   A database result object from a query to fetch node entities. If your
178
 *   query joins the {comment_entity_statistics} table so that the comment_count
179
180
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
   
Dries committed
181
 * @param $title
182
 *   (optional) A heading for the resulting list.
Dries's avatar
   
Dries committed
183
 *
184
 * @return array|false
185
186
 *   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
187
 */
188
function node_title_list(StatementInterface $result, $title = NULL) {
189
  $items = array();
190
  $num_rows = FALSE;
191
  $nids = [];
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
    $nids[] = $row->nid;
196
    $options = !empty($row->comment_count) ? array('attributes' => array('title' => \Drupal::translation()->formatPlural($row->comment_count, '1 comment', '@count comments'))) : array();
197
    $items[] = \Drupal::l($row->title, new Url('entity.node.canonical', ['node' => $row->nid], $options));
198
    $num_rows = TRUE;
Dries's avatar
   
Dries committed
199
200
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

363
  return $field;
364
}
365

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

  return $extra;
382
383
}

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

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

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

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

463
/**
464
 * Deletes a node revision.
465
 *
466
 * @param int $revision_id
467
468
469
 *   The revision ID to delete.
 */
function node_revision_delete($revision_id) {
470
  \Drupal::entityTypeManager()->getStorage('node')->deleteRevision($revision_id);
471
472
}

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

490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
/**
 * 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) {
  $variables['types'] = array();
  if (!empty($variables['content'])) {
    foreach ($variables['content'] as $type) {
      $variables['types'][$type->id()] = array(
        'type' => $type->id(),
        'add_link' => \Drupal::l($type->label(), new Url('node.add', array('node_type' => $type->id()))),
508
509
510
        'description' => array(
          '#markup' => $type->getDescription(),
        ),
511
512
513
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['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
 *   An associative array containing:
 *   - elements: An array of elements to display in view mode.
 *   - node: The node object.
569
 *   - view_mode: View mode; e.g., 'full', 'teaser', etc.
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
/**
 * 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.)
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
  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.
      $array = array(
        'min_created' => $result[0][$min_alias],
        'max_created' => $result[0][$max_alias],
      );
      \Drupal::state()->set('node.min_max_update_time', $array);
    }
639
640
641
  }
}

642
/**
643
 * Implements hook_ranking().
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
 */
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',
    ),
  );
664
665
666
  // 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')) {
667
    $ranking['recent'] = array(
668
669
      'title' => t('Recently created'),
      // Exponential decay with half life of 14% of the age range of nodes.
670
      'score' => 'EXP(-5 * (1 - (n.created - :node_oldest) / :node_range))',
671
672
673
674
      'arguments' => array(
        ':node_oldest' => $node_min_max['min_created'],
        ':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
      ),
675
676
677
678
679
    );
  }
  return $ranking;
}

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

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

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

725
/**
726
 * Finds the most recently changed nodes that are available to the current user.
727
728
729
730
 *
 * @param $number
 *   (optional) The maximum number of nodes to find. Defaults to 10.
 *
731
 * @return \Drupal\node\NodeInterface[]
732
733
 *   An array of node entities or an empty array if there are no recent nodes
 *   visible to the current user.
734
735
 */
function node_get_recent($number = 10) {
736
737
  $account = \Drupal::currentUser();
  $query = \Drupal::entityQuery('node');
738

739
  if (!$account->hasPermission('bypass node access')) {
740
741
742
    // 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.
743
    $access_query = \Drupal::entityQuery('node')
744
745
746
747
748
749
      ->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');
750
751
752
    }
    else {
      // If not, restrict the query to published nodes.
753
      $query->condition('status', NODE_PUBLISHED);
754
    }
755
  }
756
  $nids = $query
757
    ->sort('changed', 'DESC')
758
759
    ->range(0, $number)
    ->addTag('node_access')
760
    ->execute();
761

762
  $nodes = Node::loadMultiple($nids);
763
764
765
766

  return $nodes ? $nodes : array();
}

767
768
769
/**
 * Generates an array for rendering the given node.
 *
770
 * @param \Drupal\node\NodeInterface $node
771
772
 *   A node entity.
 * @param $view_mode
773
 *   (optional) View mode, e.g., 'full', 'teaser', etc. Defaults to 'full.'
774
 * @param $langcode