node.module 67.2 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\node\NodeTypeInterface;
18
use Drupal\Core\Entity\EntityInterface;
19 20
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
21
use Drupal\Core\Template\Attribute;
22
use Drupal\file\Entity\File;
23

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

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

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

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

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

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

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

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

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

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

96
  switch ($path) {
Dries's avatar
 
Dries committed
97
    case 'admin/help#node':
98 99
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
100
      $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>';
101 102
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
103 104 105
      $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>';
106
      $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>';
107
      $output .= '<dt>' . t('Administering content') . '</dt>';
108 109 110
      $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>';
111
      $output .= '<dt>' . t('User permissions') . '</dt>';
112
      $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>';
113
      $output .= '</dl>';
114
      return $output;
115

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

119
    case 'admin/structure/types/manage/%/form-display':
120 121
      $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>' ;
122

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

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

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

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

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

173 174 175 176 177
/**
 * Implements hook_entity_bundle_info().
 */
function node_entity_bundle_info() {
  $bundles = array();
178
  // Bundles must provide a human readable name so we can create help and error
179
  // messages.
180 181
  foreach (node_type_get_names() as $id => $label) {
    $bundles['node'][$id]['label'] = $label;
182
  }
183
  return $bundles;
Dries's avatar
 
Dries committed
184 185
}

186
/**
187
 * Implements hook_entity_display_alter().
188
 */
189
function node_entity_display_alter(EntityViewDisplayInterface $display, $context) {
190 191 192 193 194 195 196 197
  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);
        }
198 199
      }
    }
200 201 202 203 204 205
  }
}

/**
 * Implements hook_entity_form_display_alter().
 */
206
function node_entity_form_display_alter(EntityFormDisplayInterface $form_display, $context) {
207 208
  if ($context['entity_type'] == 'node') {
    $node_type = node_type_load($context['bundle']);
209 210 211
    // @todo Reconsider when per-bundle overrides of field definitions are
    //   possible - https://drupal.org/node/2114707.
    if (!$node_type->has_title) {
212 213
      $form_display->removeComponent('title');
    }
214 215 216
  }
}

217
/**
218
 * Entity URI callback.
219
 *
220
 * @param \Drupal\Core\Entity\EntityInterface $node
221
 *   A node entity.
222 223
 *
 * @return array
224
 *   An array with 'path' as the key and the path to the node as its value.
225
 */
226
function node_uri(EntityInterface $node) {
227
  return array(
228
    'path' => 'node/' . $node->id(),
229
  );
230 231
}

232
/**
233
 * Implements hook_admin_paths().
234 235
 */
function node_admin_paths() {
236
  if (\Drupal::config('node.settings')->get('use_admin_theme')) {
237 238 239 240 241 242
    $paths = array(
      'node/*/edit' => TRUE,
      'node/*/delete' => TRUE,
      'node/*/revisions' => TRUE,
      'node/*/revisions/*/revert' => TRUE,
      'node/*/revisions/*/delete' => TRUE,
243 244
      'node/*/translations' => TRUE,
      'node/*/translations/*' => TRUE,
245 246 247 248 249
      'node/add' => TRUE,
      'node/add/*' => TRUE,
    );
    return $paths;
  }
250 251
}

Dries's avatar
 
Dries committed
252
/**
253
 * Gathers a listing of links to nodes.
Dries's avatar
 
Dries committed
254 255
 *
 * @param $result
256
 *   A database result object from a query to fetch node entities. If your
257
 *   query joins the {comment_entity_statistics} table so that the comment_count
258 259
 *   field is available, a title attribute will be added to show the number of
 *   comments.
Dries's avatar
 
Dries committed
260
 * @param $title
261
 *   (optional) A heading for the resulting list.
Dries's avatar
 
Dries committed
262 263
 *
 * @return
264 265
 *   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
266
 */
Dries's avatar
 
Dries committed
267
function node_title_list($result, $title = NULL) {
268
  $items = array();
269
  $num_rows = FALSE;
270
  foreach ($result as $node) {
271
    // Do not use $node->label() here, because $node comes from the database.
272
    $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());
273
    $num_rows = TRUE;
Dries's avatar
 
Dries committed
274 275
  }

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

Dries's avatar
 
Dries committed
279
/**
280
 * Determines the type of marker to be displayed for a given node.
Dries's avatar
 
Dries committed
281
 *
Dries's avatar
 
Dries committed
282 283 284 285
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
286
 *
287 288
 * @return
 *   One of the MARK constants.
Dries's avatar
 
Dries committed
289
 */
290
function node_mark($nid, $timestamp) {
291

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

294
  if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
295 296
    return MARK_READ;
  }
Dries's avatar
Dries committed
297
  if (!isset($cache[$nid])) {
298
    $cache[$nid] = history_read($nid);
Dries's avatar
 
Dries committed
299
  }
300
  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
301 302
    return MARK_NEW;
  }
303
  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
304 305 306
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
 
Dries committed
307 308
}

309 310 311
/**
 * Returns a list of all the available node types.
 *
312 313
 * This list can include types that are queued for addition or deletion.
 *
314 315
 * @return array
 *   An array of node type entities, keyed by ID.
316
 *
317
 * @see node_type_load()
318 319
 */
function node_type_get_types() {
320
  return entity_load_multiple('node_type');
321 322 323
}

/**
324 325 326
 * Returns a list of available node type names.
 *
 * This list can include types that are queued for addition or deletion.
327
 *
328
 * @return array
329
 *   An array of node type labels, keyed by the node type name.
330 331
 */
function node_type_get_names() {
332
  $cid = 'node_type:names:' . language(Language::TYPE_INTERFACE)->id;
333 334 335 336 337 338 339 340 341
  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) {
342
    $config = \Drupal::config($config_name);
343 344 345 346 347 348 349
    $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;
350 351 352 353 354
}

/**
 * Returns the node type label for the passed node.
 *
355
 * @param \Drupal\Core\Entity\EntityInterface $node
356 357 358 359
 *   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.
360
 *
361 362
 * @todo Add this as generic helper method for config entities representing
 *   entity bundles.
363
 */
364 365 366
function node_get_type_label(EntityInterface $node) {
  $type = entity_load('node_type', $node->bundle());
  return $type ? $type->label() : FALSE;
367 368 369 370 371
}

/**
 * Description callback: Returns the node type description.
 *
372
 * @param \Drupal\node\NodeTypeInterface $node_type
373 374
 *   The node type object.
 *
375
 * @return string
376 377
 *   The node type description.
 */
378
function node_type_get_description(NodeTypeInterface $node_type) {
379 380 381
  return $node_type->description;
}

382
/**
383
 * Menu argument loader: Loads a node type by string.
384 385
 *
 * @param $name
386
 *   The machine name of a node type to load.
387
 *
388
 * @return \Drupal\node\NodeTypeInterface
389
 *   A node type object or NULL if $name does not exist.
390 391
 */
function node_type_load($name) {
392
  return entity_load('node_type', $name);
393
}
394

395
/**
396
 * Adds the default body field to a node type.
397
 *
398
 * @param \Drupal\node\NodeTypeInterface $type
399
 *   A node type object.
400
 * @param $label
401
 *   (optional) The label for the body instance.
402 403 404
 *
 * @return
 *   Body field instance.
405
 */
406
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
407
   // Add or remove the body field, as needed.
408
  $field = field_info_field('node', 'body');
409
  $instance = field_info_instance('node', 'body', $type->id());
410
  if (empty($field)) {
411
    $field = entity_create('field_entity', array(
412 413
      'name' => 'body',
      'entity_type' => 'node',
414
      'type' => 'text_with_summary',
415 416
    ));
    $field->save();
417
  }
418
  if (empty($instance)) {
419
    $instance = entity_create('field_instance', array(
420 421
      'field_name' => 'body',
      'entity_type' => 'node',
422
      'bundle' => $type->id(),
423 424
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
425 426
    ));
    $instance->save();
427

428 429
    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->type, 'default')
430
      ->setComponent('body', array(
431 432 433 434
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

435 436
    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->type, 'default')
437
      ->setComponent('body', array(
438 439 440 441 442
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();
    entity_get_display('node', $type->type, 'teaser')
443
      ->setComponent('body', array(
444 445 446 447
        'label' => 'hidden',
        'type' => 'text_summary_or_trimmed',
      ))
      ->save();
448
  }
449

450
  return $instance;
451
}
452

453 454 455 456 457
/**
 * Implements hook_field_extra_fields().
 */
function node_field_extra_fields() {
  $extra = array();
458
  $module_language_enabled = \Drupal::moduleHandler()->moduleExists('language');
459 460 461
  $description = t('Node module element');

  foreach (node_type_get_types() as $bundle) {
462 463
    // Add the 'language' select if Language module is enabled and the bundle
    // has multilingual support.
464
    // Visibility of the ordering of the language selector is the same as on the
465 466 467
    // node/add form.
    if ($module_language_enabled) {
      $configuration = language_get_default_configuration('node', $bundle->type);
468
      if ($configuration['language_show']) {
469
        $extra['node'][$bundle->type]['form']['langcode'] = array(
470 471 472 473 474
          'label' => t('Language'),
          'description' => $description,
          'weight' => 0,
        );
      }
475
    }
476
    $extra['node'][$bundle->type]['display']['langcode'] = array(
477 478 479 480 481
      'label' => t('Language'),
      'description' => $description,
      'weight' => 0,
      'visible' => FALSE,
    );
482
  }
483 484

  return $extra;
485 486
}

487
/**
488 489
 * Updates all nodes of one type to be of another type.
 *
490
 * @param string $old_id
491
 *   The current node type of the nodes.
492
 * @param string $new_id
493
 *   The new node type of the nodes.
494 495
 *
 * @return
496
 *   The number of nodes whose node type field was modified.
497
 */
498
function node_type_update_nodes($old_id, $new_id) {
499
  return db_update('node')
500 501
    ->fields(array('type' => $new_id))
    ->condition('type', $old_id)
502
    ->execute();
Dries's avatar
 
Dries committed
503
}
Dries's avatar
 
Dries committed
504

505
/**
506
 * Loads node entities from the database.
507 508
 *
 * This function should be used whenever you need to load more than one node
509 510
 * 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
511
 *
512 513
 * @param array $nids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
514
 * @param bool $reset
515 516
 *   (optional) Whether to reset the internal node_load() cache.  Defaults to
 *   FALSE.
Dries's avatar
 
Dries committed
517
 *
518
 * @return array
519
 *   An array of node entities indexed by nid.
520
 *
521
 * @see entity_load_multiple()
522
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
Dries's avatar
 
Dries committed
523
 */
524
function node_load_multiple(array $nids = NULL, $reset = FALSE) {
525
  return entity_load_multiple('node', $nids, $reset);
526 527 528
}

/**
529
 * Loads a node entity from the database.
530
 *
531
 * @param int $nid
532
 *   The node ID.
533
 * @param bool $reset
534 535
 *   (optional) Whether to reset the node_load_multiple() cache. Defaults to
 *   FALSE.
536
 *
537 538
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
539
 */
540
function node_load($nid = NULL, $reset = FALSE) {
541
  return entity_load('node', $nid, $reset);
542 543 544 545 546
}

/**
 * Loads a node revision from the database.
 *
547
 * @param int $vid
548 549
 *   The node revision id.
 *
550 551
 * @return \Drupal\node\NodeInterface|null
 *   A fully-populated node entity, or NULL if the node is not found.
552 553 554
 */
function node_revision_load($vid = NULL) {
  return entity_revision_load('node', $vid);
Dries's avatar
 
Dries committed
555 556
}

557
/**
558
 * Deletes a node revision.
559 560 561
 *
 * @param $revision_id
 *   The revision ID to delete.
562 563
 *
 * @return
564
 *   TRUE if the revision deletion was successful; otherwise, FALSE.
565 566
 */
function node_revision_delete($revision_id) {
567
  entity_revision_delete('node', $revision_id);
568 569
}

570
/**
571
 * Checks whether the current page is the full page view of the passed-in node.
572
 *
573
 * @param \Drupal\Core\Entity\EntityInterface $node
574
 *   A node entity.
575 576 577
 *
 * @return
 *   The ID of the node if this is a full page view, otherwise FALSE.
578
 */
579
function node_is_page(EntityInterface $node) {
580
  $page_node = menu_get_object();
581
  return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
582 583
}

584 585 586 587 588 589 590 591 592 593
/**
 * 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());
  }
}

594
/**
595
 * Implements hook_preprocess_HOOK() for block templates.
596 597
 */
function node_preprocess_block(&$variables) {
598 599
  if ($variables['configuration']['module'] == 'node') {
    switch ($variables['elements']['#plugin_id']) {
600
      case 'node_syndicate_block':
601
        $variables['attributes']['role'] = 'complementary';
602 603 604 605 606
        break;
    }
  }
}

607 608 609 610 611 612 613 614 615 616 617 618 619
/**
 * 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;
}

620
/**
621
 * Prepares variables for node templates.
622
 *
623
 * Default template: node.html.twig.
624
 *
625 626 627 628 629
 * 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
630 631 632 633
 *   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'...
634 635
 */
function template_preprocess_node(&$variables) {
636
  $variables['view_mode'] = $variables['elements']['#view_mode'];
637
  // Provide a distinct $teaser boolean.
638
  $variables['teaser'] = $variables['view_mode'] == 'teaser';
639 640 641
  $variables['node'] = $variables['elements']['#node'];
  $node = $variables['node'];

642
  $variables['date'] = format_date($node->getCreatedTime());
643 644
  $username = array(
    '#theme' => 'username',
645
    '#account' => $node->getAuthor(),
646
    '#link_options' => array('attributes' => array('rel' => 'author')),
647 648
  );
  $variables['name'] = drupal_render($username);
649

650
  $uri = $node->uri();
651
  $variables['node_url']  = url($uri['path'], $uri['options']);
652 653
  $variables['label'] = $variables['elements']['title'];
  unset($variables['elements']['title']);
654
  $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
655

656
  // Helpful $content variable for templates.
657
  $variables += array('content' => array());
658 659 660
  foreach (element_children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
661 662

  // Display post information only on certain node types.
663 664 665 666 667
  // Avoid loading the entire node type config entity here that may not exist.
  $node_type_config = \Drupal::config('node.type.' . $node->bundle());
  // Display submitted by default.
  $variables['display_submitted'] = $node_type_config->isNew() || $node_type_config->get('settings.node.submitted');
  if ($variables['display_submitted']) {
668
    $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
669
    if (theme_get_setting('features.node_user_picture')) {
670
      // To change user picture settings (e.g. image style), edit the 'compact'
671 672
      // 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.
673
      $variables['user_picture'] = user_view($node->getAuthor(), 'compact');
674 675 676 677
    }
    else {
      $variables['user_picture'] = array();
    }
678 679
  }
  else {
680
    $variables['submitted'] = '';
681
    $variables['user_picture'] = '';
682
  }
683

684
  // Add article ARIA role.
685
  $variables['attributes']['role'] = 'article';
686

687
  // Gather node classes.
688
  $variables['attributes']['class'][] = 'node';
689 690
  $variables['attributes']['class'][] = drupal_html_class('node-' . $node->bundle());
  if ($node->isPromoted()) {
691
    $variables['attributes']['class'][] = 'promoted';
692
  }
693
  if ($node->isSticky()) {
694
    $variables['attributes']['class'][] = 'sticky';
695
  }
696
  if (!$node->isPublished()) {
697
    $variables['attributes']['class'][] = 'unpublished';
698
  }
699
  if ($variables['view_mode']) {
700
    $variables['attributes']['class'][] = drupal_html_class('view-mode-' . $variables['view_mode']);
701 702
  }
  if (isset($variables['preview'])) {
703
    $variables['attributes']['class'][] = 'preview';
704
  }