node.module 70 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\String;
12
use Drupal\Core\Language\Language;
13
use Symfony\Component\HttpFoundation\Response;
14
use Drupal\Core\Cache\CacheBackendInterface;
15 16
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
17
use Drupal\Core\Datetime\DrupalDateTime;
18
use Drupal\node\NodeTypeInterface;
19
use Drupal\Core\Entity\EntityInterface;
20
use Drupal\Core\Template\Attribute;
21
use Drupal\entity\Entity\EntityDisplay;
22
use Drupal\entity\Entity\EntityFormDisplay;
23
use Drupal\file\Entity\File;
24
use Drupal\user\UserInterface;
25

26
/**
27
 * Denotes that the node is not published.
28
 */
29
const NODE_NOT_PUBLISHED = 0;
30 31

/**
32
 * Denotes that the node is published.
33
 */
34
const NODE_PUBLISHED = 1;
35 36

/**
37
 * Denotes that the node is not promoted to the front page.
38
 */
39
const NODE_NOT_PROMOTED = 0;
40 41

/**
42
 * Denotes that the node is promoted to the front page.
43
 */
44
const NODE_PROMOTED = 1;
45 46

/**
47
 * Denotes that the node is not sticky at the top of the page.
48
 */
49
const NODE_NOT_STICKY = 0;
50 51

/**
52
 * Denotes that the node is sticky at the top of the page.
53
 */
54
const NODE_STICKY = 1;
55

56
/**
57 58 59 60
 * Denotes that access is allowed for a node.
 *
 * Modules should return this value from hook_node_access() to allow access to a
 * node.
61
 */
62
const NODE_ACCESS_ALLOW = TRUE;
63 64

/**
65 66 67 68
 * Denotes that access is denied for a node.
 *
 * Modules should return this value from hook_node_access() to deny access to a
 * node.
69
 */
70
const NODE_ACCESS_DENY = FALSE;
71 72

/**
73 74 75 76
 * Denotes that access is unaffected for a node.
 *
 * Modules should return this value from hook_node_access() to indicate no
 * effect on node access.
77
 */
78
const NODE_ACCESS_IGNORE = NULL;
79

Dries's avatar
 
Dries committed
80
/**
81
 * Implements hook_help().
Dries's avatar
 
Dries committed
82
 */
83
function node_help($path, $arg) {
84 85 86
  // 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.
87
  if ($path != 'admin/reports/status/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
88
      && user_access('access administration pages') && node_access_needs_rebuild()) {
89
    if ($path == 'admin/reports/status') {
90 91 92
      $message = t('The content access permissions need to be rebuilt.');
    }
    else {
93
      $message = t('The content access permissions need to be rebuilt. <a href="@node_access_rebuild">Rebuild permissions</a>.', array('@node_access_rebuild' => url('admin/reports/status/rebuild')));
94 95 96 97
    }
    drupal_set_message($message, 'error');
  }

98
  switch ($path) {
Dries's avatar
 
Dries committed
99
    case 'admin/help#node':
100 101
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
102
      $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 online handbook entry for <a href="@node">Node module</a>.', array('@node' => 'http://drupal.org/documentation/modules/node', '@field' => url('admin/help/field'))) . '</p>';
103 104
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
105 106 107
      $output .= '<dt>' . t('Creating content') . '</dt>';
      $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' => url('admin/structure/types'))) . '</dd>';
      $output .= '<dt>' . t('Creating custom content types') . '</dt>';
108
      $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' => url('admin/structure/types/add'), '@field' => url('admin/help/field'))) . '</dd>';
109
      $output .= '<dt>' . t('Administering content') . '</dt>';
110 111 112
      $output .= '<dd>' . t('The <a href="@content">Content administration page</a> allows you to review and bulk manage your site content.', array('@content' => url('admin/content'))) . '</dd>';
      $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>';
113
      $output .= '<dt>' . t('User permissions') . '</dt>';
114
      $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' => url('admin/people/permissions', array('fragment' => 'module-node')))) . '</dd>';
115
      $output .= '</dl>';
116
      return $output;
117

118
    case 'admin/structure/types/add':
119
      return '<p>' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
120

121
    case 'admin/structure/types/manage/%/form-display':
122 123
      $type = entity_load('node_type', $arg[4]);
      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>' ;
124

125
    case 'admin/structure/types/manage/%/display':
126
      $type =  entity_load('node_type', $arg[4]);
127
      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>' .
128
        '<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>';
129

130
    case 'node/%/revisions':
131
      return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '</p>';
132

133 134
    case 'node/%/edit':
      $node = node_load($arg[1]);
135
      $type = node_type_load($node->bundle());
136
      return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
Dries's avatar
 
Dries committed
137
  }
Dries's avatar
 
Dries committed
138

139
  if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
140
    $type = node_type_load($arg[2]);
141
    return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
142
  }
Dries's avatar
 
Dries committed
143 144
}

145
/**
146
 * Implements hook_theme().
147 148 149
 */
function node_theme() {
  return array(
150
    'node' => array(
151
      'render element' => 'elements',
152
      'template' => 'node',
153
    ),
154
    'node_search_admin' => array(
155
      'render element' => 'form',
156
    ),
157
    'node_add_list' => array(
158
      'variables' => array('content' => NULL),
159
      'file' => 'node.pages.inc',
160 161
    ),
    'node_preview' => array(
162
      'variables' => array('node' => NULL),
163
      'file' => 'node.pages.inc',
164
    ),
165 166 167 168 169 170
    'node_recent_block' => array(
      'variables' => array('nodes' => NULL),
    ),
    'node_recent_content' => array(
      'variables' => array('node' => NULL),
    ),
171 172 173 174
    'node_edit_form' => array(
      'render element' => 'form',
      'template' => 'node-edit-form',
    ),
175 176 177
    'field__node__title' => array(
      'base hook' => 'field',
    ),
178 179 180
  );
}

181 182 183 184 185
/**
 * Implements hook_entity_bundle_info().
 */
function node_entity_bundle_info() {
  $bundles = array();
186
  // Bundles must provide a human readable name so we can create help and error
187
  // messages.
188 189
  foreach (node_type_get_names() as $id => $label) {
    $bundles['node'][$id]['label'] = $label;
190
  }
191
  return $bundles;
Dries's avatar
 
Dries committed
192 193
}

194
/**
195
 * Implements hook_entity_display_alter().
196
 */
197
function node_entity_display_alter(EntityDisplay $display, $context) {
198 199 200 201 202 203 204 205
  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);
        }
206 207
      }
    }
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    // @todo Manage base field displays in the YAML:
    //   https://drupal.org/node/2144919.
    $display->setComponent('title', array(
      'label' => 'hidden',
      'type' => 'text_default',
    ));
  }
}

/**
 * Implements hook_entity_form_display_alter().
 */
function node_entity_form_display_alter(EntityFormDisplay $form_display, $context) {
  if ($context['entity_type'] == 'node') {
    // @todo Manage base field displays in the YAML:
    //   https://drupal.org/node/2144919.
    $node_type = node_type_load($context['bundle']);
    if ($node_type->has_title) {
      // Title is also registered in node_field_extra_fields().
      $options = $form_display->getComponent('title') ?: array('weight' => -5);
      $options['type'] = 'text_textfield';
      $form_display->setComponent('title', $options);
    }
    else {
      $form_display->removeComponent('title');
    }
234 235 236
  }
}

237
/**
238
 * Entity URI callback.
239
 *
240
 * @param \Drupal\Core\Entity\EntityInterface $node
241
 *   A node entity.
242 243
 *
 * @return array
244
 *   An array with 'path' as the key and the path to the node as its value.
245
 */
246
function node_uri(EntityInterface $node) {
247
  return array(
248
    'path' => 'node/' . $node->id(),
249
  );
250 251
}

252
/**
253
 * Implements hook_admin_paths().
254 255
 */
function node_admin_paths() {
256
  if (\Drupal::config('node.settings')->get('use_admin_theme')) {
257 258 259 260 261 262
    $paths = array(
      'node/*/edit' => TRUE,
      'node/*/delete' => TRUE,
      'node/*/revisions' => TRUE,
      'node/*/revisions/*/revert' => TRUE,
      'node/*/revisions/*/delete' => TRUE,
263 264
      'node/*/translations' => TRUE,
      'node/*/translations/*' => TRUE,
265 266 267 268 269
      'node/add' => TRUE,
      'node/add/*' => TRUE,
    );
    return $paths;
  }
270 271
}

Dries's avatar
 
Dries committed
272
/**
273
 * Gathers a listing of links to nodes.
Dries's avatar
 
Dries committed
274 275
 *
 * @param $result
276
 *   A database result object from a query to fetch node entities. If your
277
 *   query joins the {comment_entity_statistics} table so that the comment_count
278 279
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
 
Dries committed
280
 * @param $title
281
 *   (optional) A heading for the resulting list.
Dries's avatar
 
Dries committed
282 283
 *
 * @return
284 285
 *   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
286
 */
Dries's avatar
 
Dries committed
287
function node_title_list($result, $title = NULL) {
288
  $items = array();
289
  $num_rows = FALSE;
290
  foreach ($result as $node) {
291
    // Do not use $node->label() here, because $node comes from the database.
292
    $items[] = l($node->title, 'node/' . $node->nid, !empty($node->comment_count) ? array('attributes' => array('title' => format_plural($node->comment_count, '1 comment', '@count comments'))) : array());
293
    $num_rows = TRUE;
Dries's avatar
 
Dries committed
294 295
  }

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

Dries's avatar
 
Dries committed
299
/**
300
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
301
 *
Dries's avatar
 
Dries committed
302 303 304 305
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
306
 *
307 308
 * @return
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
309
 */
310
function node_mark($nid, $timestamp) {
Dries's avatar
 
Dries committed
311
  global $user;
312
  $cache = &drupal_static(__FUNCTION__, array());
Dries's avatar
 
Dries committed
313

314
  if ($user->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
315 316
    return MARK_READ;
  }
Dries's avatar
Dries committed
317
  if (!isset($cache[$nid])) {
318
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
319
  }
320
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
321 322
    return MARK_NEW;
  }
323
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
324 325 326
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
327 328
}

329 330 331
/**
 * Returns a list of all the available node types.
 *
332 333
 * This list can include types that are queued for addition or deletion.
 *
334 335
 * @return array
 *   An array of node type entities, keyed by ID.
336
 *
337
 * @see node_type_load()
338 339
 */
function node_type_get_types() {
340
  return entity_load_multiple('node_type');
341 342 343
}

/**
344 345 346
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
347
 *
348
 * @return array
349
 *   An array of node type labels, keyed by the node type name.
350 351
 */
function node_type_get_names() {
352
  $cid = 'node_type:names:' . language(Language::TYPE_INTERFACE)->id;
353 354 355 356 357 358 359 360 361
  if ($cache = cache()->get($cid)) {
    return $cache->data;
  }
  // Not using node_type_get_types() or entity_load_multiple() here, to allow
  // this function being used in hook_entity_info() implementations.
  // @todo Consider to convert this into a generic config entity helper.
  $config_names = config_get_storage_names_with_prefix('node.type.');
  $names = array();
  foreach ($config_names as $config_name) {
362
    $config = \Drupal::config($config_name);
363 364 365 366 367 368 369
    $names[$config->get('type')] = $config->get('name');
  }
  cache()->set($cid, $names, CacheBackendInterface::CACHE_PERMANENT, array(
    'node_type' => array_keys($names),
    'node_types' => TRUE,
  ));
  return $names;
370 371 372 373 374
}

/**
 * Returns the node type label for the passed node.
 *
375
 * @param \Drupal\Core\Entity\EntityInterface $node
376 377 378 379
 *   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.
380
 *
381 382
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
383
 */
384 385 386
function node_get_type_label(EntityInterface $node) {
  $type = entity_load('node_type', $node->bundle());
  return $type ? $type->label() : FALSE;
387 388 389 390 391
}

/**
 * Description callback: Returns the node type description.
 *
392
 * @param \Drupal\node\NodeTypeInterface $node_type
393 394
 *   The node type object.
 *
395
 * @return string
396 397
 *   The node type description.
 */
398
function node_type_get_description(NodeTypeInterface $node_type) {
399 400 401
  return $node_type->description;
}

402
/**
403
 * Menu argument loader: Loads a node type by string.
404 405
 *
 * @param $name
406
 *   The machine name of a node type to load.
407
 *
408
 * @return \Drupal\node\NodeTypeInterface
409
 *   A node type object or NULL if $name does not exist.
410 411
 */
function node_type_load($name) {
412
  return entity_load('node_type', $name);
413
}
414

415
/**
416
 * Adds the default body field to a node type.
417
 *
418
 * @param \Drupal\node\NodeTypeInterface $type
419
 *   A node type object.
420
 * @param $label
421
 *   (optional) The label for the body instance.
422 423 424
 *
 * @return
 *   Body field instance.
425
 */
426
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
427
   // Add or remove the body field, as needed.
428
  $field = field_info_field('node', 'body');
429
  $instance = field_info_instance('node', 'body', $type->id());
430
  if (empty($field)) {
431
    $field = entity_create('field_entity', array(
432 433
      'name' => 'body',
      'entity_type' => 'node',
434
      'type' => 'text_with_summary',
435 436
    ));
    $field->save();
437
  }
438
  if (empty($instance)) {
439
    $instance = entity_create('field_instance', array(
440 441
      'field_name' => 'body',
      'entity_type' => 'node',
442
      'bundle' => $type->id(),
443 444
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
445 446
    ));
    $instance->save();
447

448 449
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
450
      ->setComponent('body', array(
451 452 453 454
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

455 456
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
457
      ->setComponent('body', array(
458 459 460 461 462
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
    entity_get_display('node', $type->type, 'teaser')
463
      ->setComponent('body', array(
464 465 466 467
        'label' => 'hidden',
        'type' => 'text_summary_or_trimmed',
      ))
      ->save();
468
  }
469

470
  return $instance;
471
}
472

473 474 475 476 477
/**
 * Implements hook_field_extra_fields().
 */
function node_field_extra_fields() {
  $extra = array();
478
  $module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
479 480 481 482 483 484 485 486 487 488
  $description = t('Node module element');

  foreach (node_type_get_types() as $bundle) {
    if ($bundle->has_title) {
      $extra['node'][$bundle->type]['form']['title'] = array(
        'label' => $bundle->title_label,
        'description' => $description,
        'weight' => -5,
      );
    }
489

490 491
    // Add also the 'language' select if Language module is enabled and the
    // bundle has multilingual support.
492
    // Visibility of the ordering of the language selector is the same as on the
493 494 495
    // node/add form.
    if ($module_language_enabled) {
      $configuration = language_get_default_configuration('node', $bundle->type);
496
      if ($configuration['language_show']) {
497
        $extra['node'][$bundle->type]['form']['langcode'] = array(
498 499 500 501 502
          'label' => t('Language'),
          'description' => $description,
          'weight' => 0,
        );
      }
503
    }
504
    $extra['node'][$bundle->type]['display']['langcode'] = array(
505 506 507 508 509
      'label' => t('Language'),
      'description' => $description,
      'weight' => 0,
      'visible' => FALSE,
    );
510
  }
511 512

  return $extra;
513 514
}

515
/**
516 517
 * Updates all nodes of one type to be of another type.
 *
518
 * @param string $old_id
519
 *   The current node type of the nodes.
520
 * @param string $new_id
521
 *   The new node type of the nodes.
522 523
 *
 * @return
524
 *   The number of nodes whose node type field was modified.
525
 */
526
function node_type_update_nodes($old_id, $new_id) {
527
  return db_update('node')
528 529
    ->fields(array('type' => $new_id))
    ->condition('type', $old_id)
530
    ->execute();
Dries's avatar
 
Dries committed
531
}
Dries's avatar
 
Dries committed
532

533
/**
534
 * Loads node entities from the database.
535 536
 *
 * This function should be used whenever you need to load more than one node
537 538
 * 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
539
 *
540 541
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
542
 * @param bool $reset
543 544
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries's avatar
 
Dries committed
545
 *
546
 * @return array
547
 *   An array of node entities indexed by nid.
548
 *
549
 * @see entity_load_multiple()
550
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
Dries's avatar
 
Dries committed
551
 */
552
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
553
  return entity_load_multiple('node', $nids, $reset);
554 555 556
}

/**
557
 * Loads a node entity from the database.
558
 *
559
 * @param int $nid
560
 *   The node ID.
561
 * @param bool $reset
562 563
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
564
 *
565 566
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
567
 */
568
function node_load($nid = NULL, $reset = FALSE) {
569
  return entity_load('node', $nid, $reset);
570 571 572 573 574
}

/**
 * Loads a node revision from the database.
 *
575
 * @param int $vid
576 577
 *   The node revision id.
 *
578 579
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
580 581 582
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries's avatar
 
Dries committed
583 584
}

585
/**
586
 * Deletes a node revision.
587 588 589
 *
 * @param $revision_id
 *   The revision ID to delete.
590 591
 *
 * @return
592
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
593 594
 */
function node_revision_delete($revision_id) {
595
  entity_revision_delete('node', $revision_id);
596 597
}

598
/**
599
 * Checks whether the current page is the full page view of the passed-in node.
600
 *
601
 * @param \Drupal\Core\Entity\EntityInterface $node
602
 *   A node entity.
603 604 605
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
606
 */
607
function node_is_page(EntityInterface $node) {
608
  $page_node = menu_get_object();
609
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
610 611
}

612 613 614 615 616 617 618 619 620 621
/**
 * 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.
  if ($node = menu_get_object()) {
    $variables['attributes']['class'][] = drupal_html_class('node-type-' . $node->getType());
  }
}

622
/**
623
 * Implements hook_preprocess_HOOK() for block templates.
624 625
 */
function node_preprocess_block(&$variables) {
626 627
  if ($variables['configuration']['module'] == 'node') {
    switch ($variables['elements']['#plugin_id']) {
628
      case 'node_syndicate_block':
629
        $variables['attributes']['role'] = 'complementary';
630
        break;
631
      case 'node_recent_block':
632
        $variables['attributes']['role'] = 'navigation';
633 634 635 636 637
        break;
    }
  }
}

638 639 640 641 642 643 644 645 646 647 648 649 650
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function node_theme_suggestions_node(array $variables) {
  $suggestions = array();
  $node = $variables['elements']['#node'];

  $suggestions[] = 'node__' . $node->bundle();
  $suggestions[] = 'node__' . $node->id();

  return $suggestions;
}

651
/**
652
 * Prepares variables for node templates.
653
 *
654
 * Default template: node.html.twig.
655
 *
656 657 658 659 660
 * Most themes utilize 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.
 *
 * @param array $variables
661 662 663 664
 *   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'...
665 666
 */
function template_preprocess_node(&$variables) {
667
  $variables['view_mode'] = $variables['elements']['#view_mode'];
668
  // Provide a distinct $teaser boolean.
669
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
670 671 672
  $variables['node'] = $variables['elements']['#node'];
  $node = $variables['node'];

673
  $variables['date'] = format_date($node->getCreatedTime());
674 675
  $username = array(
    '#theme' => 'username',
676
    '#account' => $node->getAuthor(),
677
    '#link_options' => array('attributes' => array('rel' => 'author')),
678 679
  );
  $variables['name'] = drupal_render($username);
680

681
  $uri = $node->uri();
682
  $variables['node_url']  = url($uri['path'], $uri['options']);
683 684
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
685
  $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
686

687
  // Helpful $content variable for templates.
688
  $variables += array('content' => array());
689 690 691
  foreach (element_children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
692

693
  // Make the field variables available with the appropriate language.
694
  field_attach_preprocess($node, $variables['content'], $variables);
695

696
  // Display post information only on certain node types.
697
  // Avoid loading the entire node type config entity here.
698
  $submitted = \Drupal::config('node.type.' . $node->bundle())->get('settings.node.submitted') ?: TRUE;
699
  if ($submitted) {
700
    $variables['display_submitted'] = TRUE;
701
    $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
702
    if (theme_get_setting('features.node_user_picture')) {
703
      // To change user picture settings (e.g. image style), edit the 'compact'
704 705
      // 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.
706
      $variables['user_picture'] = user_view($node->getAuthor(), 'compact');
707 708 709 710
    }
    else {
      $variables['user_picture'] = array();
    }
711 712
  }
  else {
713
    $variables['display_submitted'] = FALSE;
714
    $variables['submitted'] = '';
715
    $variables['user_picture'] = '';
716
  }
717

718
  // Add article ARIA role.
719
  $variables['attributes']['role'] = 'article';
720