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

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

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

33
/**
34
 * Denotes that the node is not published.
35
 */
36
const NODE_NOT_PUBLISHED = 0;
37 38

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

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

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

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

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

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

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

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

104 105
    case 'entity.entity_form_display.node.default':
    case 'entity.entity_form_display.node.form_mode':
106
      $type = $route_match->getParameter('node_type');
107
      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>' ;
108

109 110
    case 'entity.entity_view_display.node.default':
    case 'entity.entity_view_display.node.view_mode':
111
      $type = $route_match->getParameter('node_type');
112
      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>' .
113
        '<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>';
114

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

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

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

131
/**
132
 * Implements hook_theme().
133 134 135
 */
function node_theme() {
  return array(
136
    'node' => array(
137
      'render element' => 'elements',
138
    ),
139
    'node_add_list' => array(
140
      'variables' => array('content' => NULL),
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
  }
}

174
/**
175
 * Gathers a listing of links to nodes.
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.
182
 * @param $title
183
 *   (optional) A heading for the resulting list.
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.
188
 */
189
function node_title_list(StatementInterface $result, $title = NULL) {
190
  $items = array();
191
  $num_rows = FALSE;
192
  $nids = [];
193 194 195
  foreach ($result as $row) {
    // Do not use $node->label() or $node->urlInfo() here, because we only have
    // database rows, not actual nodes.
196
    $nids[] = $row->nid;
197
    $options = !empty($row->comment_count) ? array('attributes' => array('title' => \Drupal::translation()->formatPlural($row->comment_count, '1 comment', '@count comments'))) : array();
198
    $items[] = \Drupal::l($row->title, new Url('entity.node.canonical', ['node' => $row->nid], $options));
199
    $num_rows = TRUE;
Dries's avatar
Dries committed
200 201
  }

202
  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
203 204
}

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

218
  $cache = &drupal_static(__FUNCTION__, array());
219

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

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

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

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

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

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

312
/**
313
 * Adds the default body field to a node type.
314
 *
315
 * @param \Drupal\node\NodeTypeInterface $type
316
 *   A node type object.
317
 * @param string $label
318
 *   (optional) The label for the body instance.
319
 *
320
 * @return \Drupal\field\Entity\FieldConfig Body field.
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 327
  $field = FieldConfig::loadByName('node', $type->id(), 'body');
  if (empty($field)) {
    $field = entity_create('field_config', array(
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);
397
}
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.
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.
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
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 460
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
461 462
}

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

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

493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
/**
 * 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()))),
511 512 513
        'description' => array(
          '#markup' => $type->getDescription(),
        ),
514 515 516 517 518
      );
    }
  }
}

519 520 521 522 523
/**
 * 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.
524
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
525
    $variables['node_type'] = $node->getType();
526 527 528
  }
}

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

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

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

  return $suggestions;
}

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

586
  $variables['url'] = $node->url('canonical', array(
587 588
    'language' => $node->language(),
  ));
589 590
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
591 592 593 594
  // 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'))));
595

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

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

616
  // Add article ARIA role.
617
  $variables['attributes']['role'] = 'article';
618 619
}

620 621 622 623 624 625 626
/**
 * 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.)
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
  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);
    }
642 643 644
  }
}

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

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

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

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

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

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

764
  $nodes = Node::loadMultiple($nids);
765 766 767 768

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

769 770 771
/**
 * Generates an array for rendering the given node.
 *
772
 * @param \Drupal\node\NodeInterface $node
773 774
 *   A node entity.
 * @param $view_mode
775
 *   (optional) View mode, e.g., 'full', 'teaser', etc. Defaults to 'full.'
776
 * @param $langcode
777 778
 *   (optional) A language code to use for rendering. Defaults to NULL which is
 *   the global content language of the current request.
779 780 781 782
 *
 * @return
 *   An array as expected by drupal_render().
 */
783
function node_view(NodeInterface $node, $view_mode = 'full', $langcode = NULL) {
784 785 786
  return entity_view($node, $view_mode, $langcode);
}

787
/**
788
 * Constructs a drupal_render() style array from an array of loaded nodes.
789 790
 *
 * @param $nodes
791
 *   An array of nodes as returned by Node::loadMultiple().
792
 * @param $view_mode
793
 *   (optional) View mode, e.g., 'full', 'teaser', etc. Defaults to 'teaser.'
794 795 796 797
 * @param $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 *
798 799 800
 * @return
 *   An array in the format expected by drupal_render().
 */
801 802
function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) {
  return entity_view_multiple($nodes, $view_mode, $langcode);
803 804
}

805
/**
806
 * Implements hook_page_top().
807
 */
808
function node_page_top(array &$page) {
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
  // Add 'Back to content editing' link on preview page.
  $route_match = \Drupal::routeMatch();
  if ($route_match->getRouteName() == 'entity.node.preview') {
    $page['page_top']['node_preview'] = array(
      '#type' => 'container',
      '#attributes' => array(
        'class' => array('node-preview-container', 'container-inline')
      ),
    );

    $form = \Drupal::formBuilder()->getForm('\Drupal\node\Form\NodePreviewForm', $route_match->getParameter('node_preview'));
    $page['page_top']['node_preview']['view_mode'] = $form;
  }
}

824 825 826 827 828 829 830
/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the theme form to use the admin theme on node editing.
 *
 * @see node_form_system_themes_admin_form_submit()
 */
831
function node_form_system_themes_admin_form_alter(&$form, FormStateInterface $form_state, $form_id) {
832
  $form['admin_theme']['use_admin_theme'] = array(
833 834
    '#type' => 'checkbox',
    '#title' => t('Use the administration theme when editing or creating content'),
835
    '#default_value' => \Drupal::configFactory()->getEditable('node.settings')->get('use_admin_theme'),
836 837 838 839 840 841 842 843 844
  );
  $form['#submit'][] = 'node_form_system_themes_admin_form_submit';
}

/**
 * Form submission handler for system_themes_admin_form().
 *
 * @see node_form_system_themes_admin_form_alter()
 */
845
function node_form_system_themes_admin_form_submit($form, FormStateInterface $form_state) {
846
  \Drupal::configFactory()->getEditable('node.settings')
847
    ->set('use_admin_theme', $form_state->getValue('use_admin_theme'))
848
    ->save();
849
  \Drupal::service('router.builder')->setRebuildNeeded();
850 851
}

852 853 854 855 856
/**
 * @defgroup node_access Node access rights
 * @{
 * The node access system determines who can do what to which nodes.
 *
857
 * In determining access rights for a node, \Drupal\node\NodeAccessControlHandler
858 859 860
 * first checks whether the user has the "bypass node access" permission. Such
 * users have unrestricted access to all nodes. user 1 will always pass this
 * check.
861 862
 *
 * Next, all implementations of hook_node_access() will be called. Each
863 864
 * implementation may explicitly allow, explicitly forbid, or ignore the access
 * request. If at least one module says to forbid the request, it will be
865 866
 * rejected. If no modules deny the request and at least one says to allow it,
 * the request will be permitted.
867 868 869 870 871 872 873
 *
 * If all modules ignore the access request, then the node_access table is used
 * to determine access. All node access modules are queried using
 * hook_node_grants() to assemble a list of "grant IDs" for the user. This list
 * is compared against the table. If any row contains the node ID in question
 * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a
 * value of TRUE for the operation in question, then access is granted. Note
874 875
 * that this table is a list of grants; any matching row is sufficient to grant
 * access to the node.
876
 *
877 878 879 880
 * In node listings (lists of nodes generated from a select query, such as the
 * default home page at path 'node', an RSS feed, a recent content block, etc.),
 * the process above is followed except that hook_node_access() is not called on
 * each node for performance reasons and for proper functioning of the pager
881 882
 * system. When adding a node listing to your module, be sure to use an entity
 * query, which will add a tag of "node_access". This will allow modules dealing
883
 * with node access to ensure only nodes to which the user has access are
884 885 886
 * retrieved, through the use of hook_query_TAG_alter(). See the
 * @link entity_api Entity API topic @endlink for more information on entity
 * queries.
887
 *
888 889 890 891 892 893 894
 * Note: Even a single module returning an AccessResultInterface object from
 * hook_node_access() whose isForbidden() method equals TRUE will block access
 * to the node. Therefore, implementers should take care to not deny access
 * unless they really intend to. Unless a module wishes to actively forbid
 * access it should return an AccessResultInterface object whose isAllowed() nor
 * isForbidden() methods return TRUE, to allow other modules or the node_access
 * table to control access.
895 896 897 898 899
 *
 * To see how to write a node access module of your own, see
 * node_access_example.module.
 */

900
/**
901
 * Implements hook_node_access().
902
 */
903
function node_node_access(NodeInterface $node, $op, $account) {
904
  $type = $node->bundle();
905

906 907 908
  switch ($op) {
    case 'create':
      return AccessResult::allowedIfHasPermission($account, 'create ' . $type . ' content');
909

910 911
    case 'update':
      if ($account->hasPermission('edit any ' . $type . ' content', $account)) {
912
        return AccessResult::allowed()->cachePerPermissions();
913 914
      }
      else {
915
        return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
916
      }
917

918 919
    case 'delete':
      if ($account->hasPermission('delete any ' . $type . ' content', $account)) {
920
        return AccessResult::allowed()->cachePerPermissions();
921 922
      }
      else {
923
        return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
924
      }
925

926 927
    default:
      // No opinion.
928
      return AccessResult::neutral();
929
  }
930 931
}

932
/**
933
 * Fetches an array of permission IDs granted to the given user ID.
934 935
 *
 * The implementation here provides only the universal "all" grant. A node
936 937
 * access module should implement hook_node_grants() to provide a grant list for
 * the user.
938
 *
939 940 941
 * After the default grants have been loaded, we allow modules to alter the
 * grants array by reference. This hook allows for complex business logic to be
 * applied when integrating multiple node access modules.
942
 *
943
 * @param string $op
944
 *   The operation that the user is trying to perform.
945 946
 * @param \Drupal\Core\Session\AccountInterface $account
 *   The account object for the user performing the operation.
947
 *
948
 * @return array
949 950 951
 *   An associative array in which the keys are realms, and the values are
 *   arrays of grants for those realms.
 */
952
function node_access_grants($op, AccountInterface $account) {
953
  // Fetch node access grants from other modules.
954
  $grants = \Drupal::moduleHandler()->invokeAll('node_grants', array($account, $op));
955
  // Allow modules to alter the assigned grants.
956
  \Drupal::moduleHandler()->alter('node_grants', $grants, $account, $op);
957 958

  return array_merge(array('all' => array(0)), $grants);
959 960 961
}

/**
962 963
 * Determines whether the user has a global viewing grant for all nodes.
 *
964 965 966 967 968 969 970 971 972 973
 * Checks to see whether any module grants global 'view' access to a user
 * account; global 'view' access is encoded in the {node_access} table as a
 * grant with nid=0. If no node access modules are enabled, node.module defines
 * such a global 'view' access grant.
 *
 * This function is called when a node listing query is tagged with
 * 'node_access'; when this function returns TRUE, no node access joins are
 * added to the query.
 *
 * @param $account
974
 *   (optional) The user object for the user whose access is being checked. If
975
 *   omitted, the current user is used. Defaults to NULL.
976 977 978 979 980
 *
 * @return
 *   TRUE if 'view' access to all nodes is granted, FALSE otherwise.
 *
 * @see hook_node_grants()
981
 * @see node_query_node_access_alter()
982
 */
983
function node_access_view_all_nodes($account = NULL) {
984

985
  if (!$account) {
986
    $account = \Drupal::currentUser();
987
  }
988

989
  // Statically cache results in an array keyed by $account->id().
990
  $access = &drupal_static(__FUNCTION__);
991 992
  if (isset($access[$account->id()])) {
    return $access[$account->id()];
993
  }
994

995
  // If no modules implement the node access system, access is always TRUE.
996
  if (!\Drupal::moduleHandler()->getImplementations('node_grants')) {
997
    $access[$account->id()] = TRUE;
998 999
  }
  else {
1000
    $access[$account->id()] = \Drupal::entityManager()->getAccessControlHandler('node')->checkAllGrants($account);
1001 1002
  }

1003
  return $access[$account->id()];
1004
}
1005

1006

1007
/**
1008
 * Implements hook_query_TAG_alter().
1009
 *
1010 1011
 * This is the hook_query_alter() for queries tagged with 'node_access'. It adds
 * node access checks for the user account given by the 'account' meta-data (or
catch's avatar